package org.fxmisc.easybind;
import java.util.Collection;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;
import javafx.beans.property.Property;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import org.fxmisc.easybind.monadic.MonadicBinding;
import org.fxmisc.easybind.monadic.MonadicObservableValue;
import org.fxmisc.easybind.monadic.PropertyBinding;
import org.fxmisc.easybind.select.SelectBuilder;
/**
* Methods for easy creation of bindings.
*/
public class EasyBind {
@FunctionalInterface
public interface TriFunction<A, B, C, R> {
R apply(A a, B b, C c);
}
@FunctionalInterface
public interface TetraFunction<A, B, C, D, R> {
R apply(A a, B b, C c, D d);
}
@FunctionalInterface
public interface PentaFunction<A, B, C, D, E, R> {
R apply(A a, B b, C c, D d, E e);
}
@FunctionalInterface
public interface HexaFunction<A, B, C, D, E, F, R> {
R apply(A a, B b, C c, D d, E e, F f);
}
/**
* Creates a thin wrapper around an observable value to make it monadic.
* @param o ObservableValue to wrap
* @return {@code o} if {@code o} is already monadic, or a thin monadic
* wrapper around {@code o} otherwise.
*/
public static <T> MonadicObservableValue<T> monadic(ObservableValue<T> o) {
if(o instanceof MonadicObservableValue) {
return (MonadicObservableValue<T>) o;
} else {
return new MonadicWrapper<>(o);
}
}
public static <T> MonadicBinding<T> filter(
ObservableValue<T> src,
Predicate<? super T> p) {
return new PreboundBinding<T>(src) {
@Override
protected T computeValue() {
T val = src.getValue();
return (val != null && p.test(val)) ? val : null;
}
};
}
public static <T, U> MonadicBinding<U> map(
ObservableValue<T> src,
Function<? super T, ? extends U> f) {
return new PreboundBinding<U>(src) {
@Override
protected U computeValue() {
T baseVal = src.getValue();
return baseVal != null ? f.apply(baseVal) : null;
}
};
}
public static <T, U> MonadicBinding<U> flatMap(
ObservableValue<T> src,
Function<? super T, ? extends ObservableValue<U>> f) {
return new FlatMapBinding<>(src, f);
}
public static <T, U> PropertyBinding<U> selectProperty(
ObservableValue<T> src,
Function<? super T, ? extends Property<U>> f) {
return new FlatMapProperty<>(src, f);
}
public static <T> MonadicBinding<T> orElse(ObservableValue<? extends T> src, T other) {
return new PreboundBinding<T>(src) {
@Override
protected T computeValue() {
T val = src.getValue();
return val != null ? val : other;
}
};
}
public static <T> MonadicBinding<T> orElse(
ObservableValue<? extends T> src,
ObservableValue<? extends T> other) {
return new FirstNonNullBinding<>(src, other);
}
public static <T, U> ObservableList<U> map(
ObservableList<? extends T> sourceList,
Function<? super T, ? extends U> f) {
return new MappedList<>(sourceList, f);
}
public static <A, B, R> MonadicBinding<R> combine(
ObservableValue<A> src1,
ObservableValue<B> src2,
BiFunction<A, B, R> f) {
return new PreboundBinding<R>(src1, src2) {
@Override
protected R computeValue() {
return f.apply(src1.getValue(), src2.getValue());
}
};
}
public static <A, B, C, R> MonadicBinding<R> combine(
ObservableValue<A> src1,
ObservableValue<B> src2,
ObservableValue<C> src3,
TriFunction<A, B, C, R> f) {
return new PreboundBinding<R>(src1, src2, src3) {
@Override
protected R computeValue() {
return f.apply(
src1.getValue(), src2.getValue(), src3.getValue());
}
};
}
public static <A, B, C, D, R> MonadicBinding<R> combine(
ObservableValue<A> src1,
ObservableValue<B> src2,
ObservableValue<C> src3,
ObservableValue<D> src4,
TetraFunction<A, B, C, D, R> f) {
return new PreboundBinding<R>(src1, src2, src3, src4) {
@Override
protected R computeValue() {
return f.apply(
src1.getValue(), src2.getValue(),
src3.getValue(), src4.getValue());
}
};
}
public static <A, B, C, D, E, R> MonadicBinding<R> combine(
ObservableValue<A> src1,
ObservableValue<B> src2,
ObservableValue<C> src3,
ObservableValue<D> src4,
ObservableValue<E> src5,
PentaFunction<A, B, C, D, E, R> f) {
return new PreboundBinding<R>(src1, src2, src3, src4, src5) {
@Override
protected R computeValue() {
return f.apply(
src1.getValue(), src2.getValue(), src3.getValue(),
src4.getValue(), src5.getValue());
}
};
}
public static <A, B, C, D, E, F, R> MonadicBinding<R> combine(
ObservableValue<A> src1,
ObservableValue<B> src2,
ObservableValue<C> src3,
ObservableValue<D> src4,
ObservableValue<E> src5,
ObservableValue<F> src6,
HexaFunction<A, B, C, D, E, F, R> f) {
return new PreboundBinding<R>(src1, src2, src3, src4, src5, src6) {
@Override
protected R computeValue() {
return f.apply(
src1.getValue(), src2.getValue(), src3.getValue(),
src4.getValue(), src5.getValue(), src6.getValue());
}
};
}
public static <T, R> MonadicBinding<R> combine(
ObservableList<? extends ObservableValue<? extends T>> list,
Function<? super Stream<T>, ? extends R> f) {
return new ListCombinationBinding<>(list, f);
}
public static <T> SelectBuilder<T> select(ObservableValue<T> selectionRoot) {
return SelectBuilder.startAt(selectionRoot);
}
/**
* Sets up automatic binding and unbinding of {@code target} to/from
* {@code source}, based on the changing value of {@code condition}.
* In other words, this method starts watching {@code condition} for
* changes. When {@code condition} changes to {@code true}, {@code target}
* is bound to {@code source}. When {@code condition} changes to
* {@code false}, {@code target} is unbound. This keeps happening until
* either {@code unsubscribe()} is called on the returned subscription,
* or {@code target} is garbage collected.
* @param target target of the conditional binding
* @param source source of the conditional binding
* @param condition controls when to bind and unbind target to/from source
* @return a subscription that can be used to dispose the conditional
* binding set up by this method, i.e. stop observing {@code condition}
* and unbind {@code target} from {@code source}.
* @deprecated Since 1.0.2. Use {@code when(condition).bind(target, source)}
* instead.
*/
@Deprecated
public static <T> Subscription bindConditionally(
Property<T> target,
ObservableValue<? extends T> source,
ObservableValue<Boolean> condition) {
return new ConditionalBinding<>(target, source, condition);
}
/**
* Sync the content of the {@code target} list with the {@code source} list.
* @return a subscription that can be used to stop syncing the lists.
*/
public static <T> Subscription listBind(
List<? super T> target,
ObservableList<? extends T> source) {
target.clear();
target.addAll(source);
ListChangeListener<? super T> listener = change -> {
while(change.next()) {
int from = change.getFrom();
int to = change.getTo();
if(change.wasPermutated()) {
target.subList(from, to).clear();
target.addAll(from, source.subList(from, to));
} else {
target.subList(from, from + change.getRemovedSize()).clear();
target.addAll(from, source.subList(from, from + change.getAddedSize()));
}
}
};
source.addListener(listener);
return () -> source.removeListener(listener);
}
/**
* Entry point for creating conditional bindings.
*/
public static When when(ObservableValue<Boolean> condition) {
return new When(condition);
}
/**
* Adds {@code element} to {@code collection} when {@code condition} is
* {@code true} and removes it from {@code collection} when
* {@code condition} is {@code false}.
* @return a subscription that can be used to stop observing
* {@code condition} and manipulating {@code collection}.
*/
public static <T> Subscription includeWhen(
Collection<T> collection,
T element,
ObservableValue<Boolean> condition) {
return subscribe(condition, new Consumer<Boolean>() {
private boolean included = false;
@Override
public void accept(Boolean value) {
if(value && !included) {
included = collection.add(element);
} else if(!value && included) {
collection.remove(element);
included = false;
}
}
});
}
/**
* Invokes {@code subscriber} for the current and every new value of
* {@code observable}.
* @param observable observable value to subscribe to
* @param subscriber action to invoke for values of {@code observable}.
* @return a subscription that can be used to stop invoking subscriber
* for any further {@code observable} changes.
*/
public static <T> Subscription subscribe(ObservableValue<T> observable, Consumer<? super T> subscriber) {
subscriber.accept(observable.getValue());
ChangeListener<? super T> listener = (obs, oldValue, newValue) -> subscriber.accept(newValue);
observable.addListener(listener);
return () -> observable.removeListener(listener);
}
}