Skip to content
Open
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
6 changes: 6 additions & 0 deletions src/main/java/org/fxmisc/easybind/EasyBind.java
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,12 @@ protected R computeValue() {
public static <T, R> MonadicBinding<R> combine(
ObservableList<? extends ObservableValue<? extends T>> list,
Function<? super Stream<T>, ? extends R> f) {
return new ObservablesListCombinationBinding<>(list, f);
}

public static <T, R> MonadicBinding<R> weakCombine(
ObservableList<? extends T> list,
Function<? super Stream<T>, ? extends R> f) {
return new ListCombinationBinding<>(list, f);
}

Expand Down
39 changes: 10 additions & 29 deletions src/main/java/org/fxmisc/easybind/ListCombinationBinding.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,60 +3,41 @@
import java.util.function.Function;
import java.util.stream.Stream;

import org.fxmisc.easybind.monadic.MonadicBinding;

import javafx.beans.InvalidationListener;
import javafx.beans.WeakInvalidationListener;
import javafx.beans.binding.ObjectBinding;
import javafx.beans.value.ObservableValue;
import javafx.collections.ListChangeListener;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import javafx.collections.WeakListChangeListener;

import org.fxmisc.easybind.monadic.MonadicBinding;

class ListCombinationBinding<T, U> extends ObjectBinding<U> implements
MonadicBinding<U> {

private final ListChangeListener<ObservableValue<? extends T>> listListener = ch -> sourceChanged(ch);
private final InvalidationListener elemListener = obs -> elementInvalidated();
private final InvalidationListener invalidationListener = obs -> invalidate();

private final WeakListChangeListener<ObservableValue<? extends T>> weakListListener = new WeakListChangeListener<>(listListener);
private final WeakInvalidationListener weakElemListener = new WeakInvalidationListener(elemListener);
private final WeakInvalidationListener weakInvalidationListener = new WeakInvalidationListener(
invalidationListener);

private final ObservableList<? extends ObservableValue<? extends T>> source;
private final ObservableList<? extends T> source;
private final Function<? super Stream<T>, ? extends U> combiner;

public ListCombinationBinding(
ObservableList<? extends ObservableValue<? extends T>> list,
ObservableList<? extends T> list,
Function<? super Stream<T>, ? extends U> f) {
source = list;
combiner = f;

source.addListener(weakListListener);
source.forEach(elem -> elem.addListener(weakElemListener));
source.addListener(weakInvalidationListener);
}

@Override
protected U computeValue() {
return combiner.apply(source.stream().map(obs -> obs.getValue()));
return combiner.apply(source.stream().map(Function.identity()));
}

@Override
public void dispose() {
source.forEach(elem -> elem.removeListener(weakElemListener));
source.removeListener(weakListListener);
source.removeListener(weakInvalidationListener);
}

private void sourceChanged(
Change<? extends ObservableValue<? extends T>> ch) {
while(ch.next()) {
ch.getRemoved().forEach(elem -> elem.removeListener(weakElemListener));
ch.getAddedSubList().forEach(elem -> elem.addListener(weakElemListener));
invalidate();
}
}

private void elementInvalidated() {
invalidate();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package org.fxmisc.easybind;

import java.util.function.Function;
import java.util.stream.Stream;

import javafx.beans.InvalidationListener;
import javafx.beans.WeakInvalidationListener;
import javafx.beans.binding.ObjectBinding;
import javafx.beans.value.ObservableValue;
import javafx.collections.ListChangeListener;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import javafx.collections.WeakListChangeListener;

import org.fxmisc.easybind.monadic.MonadicBinding;

class ObservablesListCombinationBinding<T, U> extends ObjectBinding<U> implements
MonadicBinding<U> {

private final ListChangeListener<ObservableValue<? extends T>> listListener = ch -> sourceChanged(ch);
private final InvalidationListener elemListener = obs -> elementInvalidated();

private final WeakListChangeListener<ObservableValue<? extends T>> weakListListener = new WeakListChangeListener<>(listListener);
private final WeakInvalidationListener weakElemListener = new WeakInvalidationListener(elemListener);

private final ObservableList<? extends ObservableValue<? extends T>> source;
private final Function<? super Stream<T>, ? extends U> combiner;

public ObservablesListCombinationBinding(
ObservableList<? extends ObservableValue<? extends T>> list,
Function<? super Stream<T>, ? extends U> f) {
source = list;
combiner = f;

source.addListener(weakListListener);
source.forEach(elem -> elem.addListener(weakElemListener));
}

@Override
protected U computeValue() {
return combiner.apply(source.stream().map(obs -> obs.getValue()));
}

@Override
public void dispose() {
source.forEach(elem -> elem.removeListener(weakElemListener));
source.removeListener(weakListListener);
}

private void sourceChanged(
Change<? extends ObservableValue<? extends T>> ch) {
boolean changed = false;
while(ch.next()) {
ch.getRemoved().forEach(elem -> elem.removeListener(weakElemListener));
ch.getAddedSubList().forEach(elem -> elem.addListener(weakElemListener));
changed = true;
}
if (changed) {
invalidate();
}
}

private void elementInvalidated() {
invalidate();
}
}
27 changes: 10 additions & 17 deletions src/test/java/org/fxmisc/easybind/CombineListTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

import static org.junit.Assert.*;
import javafx.beans.binding.Binding;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;

Expand All @@ -13,19 +11,19 @@ public class CombineListTest {

@Test
public void test() {
ObservableList<Property<Integer>> list = FXCollections.observableArrayList();
Binding<Integer> sum = EasyBind.combine(list, stream -> stream.reduce((a, b) -> a + b).orElse(0));
ObservableList<Integer> list = FXCollections.observableArrayList();
Binding<Integer> sum = EasyBind.weakCombine(list, stream -> stream.reduce((a, b) -> a + b).orElse(0));

Counter counter = new Counter();
sum.addListener(obs -> counter.inc());

assertEquals(0, sum.getValue().intValue());
assertEquals(0, counter.getAndReset());

Property<Integer> a = new SimpleObjectProperty<>(1);
Property<Integer> b = new SimpleObjectProperty<>(2);
Property<Integer> c = new SimpleObjectProperty<>(4);
Property<Integer> d = new SimpleObjectProperty<>(8);
Integer a = 1;
Integer b = 2;
Integer c = 4;
Integer d = 8;

// check that added items are reflected
list.add(a);
Expand All @@ -37,27 +35,22 @@ public void test() {
assertEquals(15, sum.getValue().intValue());
assertEquals(1, counter.getAndReset());

// check that changing an item's value is reflected
b.setValue(16);
assertEquals(29, sum.getValue().intValue());
assertEquals(1, counter.getAndReset());

// check that value removal is reflected
list.remove(b);
list.remove(d);
assertEquals(5, sum.getValue().intValue());
assertEquals(1, counter.getAndReset());

// check that changing a removed item does not affect or invalidate the sum
b.setValue(32);
d.setValue(64);
b = 32;
d = 64;
assertEquals(5, sum.getValue().intValue());
assertEquals(0, counter.getAndReset());

// check that no more invalidations arrive after disposal
sum.dispose();
a.setValue(2);
c.setValue(8);
a = 2;
c = 8;
assertEquals(0, counter.getAndReset());
}

Expand Down
64 changes: 64 additions & 0 deletions src/test/java/org/fxmisc/easybind/CombineObservablesListTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package org.fxmisc.easybind;

import static org.junit.Assert.*;
import javafx.beans.binding.Binding;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;

import org.junit.Test;

public class CombineObservablesListTest {

@Test
public void test() {
ObservableList<Property<Integer>> list = FXCollections.observableArrayList();
Binding<Integer> sum = EasyBind.combine(list, stream -> stream.reduce((a, b) -> a + b).orElse(0));

Counter counter = new Counter();
sum.addListener(obs -> counter.inc());

assertEquals(0, sum.getValue().intValue());
assertEquals(0, counter.getAndReset());

Property<Integer> a = new SimpleObjectProperty<>(1);
Property<Integer> b = new SimpleObjectProperty<>(2);
Property<Integer> c = new SimpleObjectProperty<>(4);
Property<Integer> d = new SimpleObjectProperty<>(8);

// check that added items are reflected
list.add(a);
list.add(b);
assertEquals(3, sum.getValue().intValue());
assertEquals(1, counter.getAndReset());
list.add(c);
list.add(d);
assertEquals(15, sum.getValue().intValue());
assertEquals(1, counter.getAndReset());

// check that changing an item's value is reflected
b.setValue(16);
assertEquals(29, sum.getValue().intValue());
assertEquals(1, counter.getAndReset());

// check that value removal is reflected
list.remove(b);
list.remove(d);
assertEquals(5, sum.getValue().intValue());
assertEquals(1, counter.getAndReset());

// check that changing a removed item does not affect or invalidate the sum
b.setValue(32);
d.setValue(64);
assertEquals(5, sum.getValue().intValue());
assertEquals(0, counter.getAndReset());

// check that no more invalidations arrive after disposal
sum.dispose();
a.setValue(2);
c.setValue(8);
assertEquals(0, counter.getAndReset());
}

}