package org.fxmisc.easybind;
import java.util.function.Function;
import javafx.beans.InvalidationListener;
import javafx.beans.WeakInvalidationListener;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.ObjectBinding;
import javafx.beans.property.Property;
import javafx.beans.value.ObservableValue;
import org.fxmisc.easybind.monadic.MonadicBinding;
import org.fxmisc.easybind.monadic.PropertyBinding;
abstract class FlatMapBindingBase<T, U, O extends ObservableValue<U>>
extends ObjectBinding<U> implements MonadicBinding<U> {
private final ObservableValue<T> src;
private final Function<? super T, O> mapper;
// need to retain strong reference to listeners, so that they don't get garbage collected
private final InvalidationListener srcListener = obs -> srcInvalidated();
private final InvalidationListener mappedListener = obs -> mappedInvalidated();
private final InvalidationListener weakSrcListener = new WeakInvalidationListener(srcListener);
private final InvalidationListener weakMappedListener = new WeakInvalidationListener(mappedListener);
private O mapped = null;
private Subscription mappedSubscription = null;
public FlatMapBindingBase(ObservableValue<T> src, Function<? super T, O> f) {
this.src = src;
this.mapper = f;
src.addListener(weakSrcListener);
}
@Override
public final void dispose() {
src.removeListener(weakSrcListener);
disposeMapped();
}
@Override
protected final U computeValue() {
setupTargetObservable();
return mapped != null ? mapped.getValue() : null;
}
private void setupTargetObservable() {
if(mapped == null) {
T baseVal = src.getValue();
if(baseVal != null) {
mapped = mapper.apply(baseVal);
mappedSubscription = observeTargetObservable(mapped);
}
}
}
protected O getTargetObservable() {
setupTargetObservable();
return mapped;
}
protected Subscription observeTargetObservable(O target) {
target.addListener(weakMappedListener);
return () -> target.removeListener(weakMappedListener);
}
private void disposeMapped() {
if(mapped != null) {
mappedSubscription.unsubscribe();
mappedSubscription = null;
mapped = null;
}
}
private void mappedInvalidated() {
invalidate();
}
protected void srcInvalidated() {
disposeMapped();
invalidate();
}
}
class FlatMapBinding<T, U, O extends ObservableValue<U>> extends FlatMapBindingBase<T, U, O> {
public FlatMapBinding(ObservableValue<T> src, Function<? super T, O> f) {
super(src, f);
}
}
class FlatMapProperty<T, U, O extends Property<U>> extends FlatMapBindingBase<T, U, O> implements PropertyBinding<U> {
private ObservableValue<? extends U> boundTo = null;
private boolean resetOnUnbind = false;
private U resetTo = null;
public FlatMapProperty(ObservableValue<T> src, Function<? super T, O> f) {
super(src, f);
}
@Override
protected Subscription observeTargetObservable(O mapped) {
if(boundTo != null) {
mapped.bind(boundTo);
}
Subscription s1 = super.observeTargetObservable(mapped);
Subscription s2 = () -> {
if(boundTo != null) {
mapped.unbind();
if(resetOnUnbind) {
mapped.setValue(resetTo);
}
}
};
return s1.and(s2);
}
@Override
protected void srcInvalidated() {
super.srcInvalidated();
// if bound, make sure to rebind eagerly
if(boundTo != null) {
getTargetObservable();
}
}
@Override
public void setValue(U value) {
Property<U> target = getTargetObservable();
if(target != null) {
target.setValue(value);
}
}
@Override
public void bind(ObservableValue<? extends U> other) {
Property<U> target = getTargetObservable();
if(target != null) {
target.bind(other);
}
boundTo = other;
resetOnUnbind = false;
resetTo = null;
}
@Override
public void bind(ObservableValue<? extends U> other, U resetToOnUnbind) {
Property<U> target = getTargetObservable();
if(target != null) {
target.bind(other);
}
boundTo = other;
resetOnUnbind = true;
resetTo = resetToOnUnbind;
}
@Override
public boolean isBound() {
return boundTo != null ||
(getTargetObservable() != null && getTargetObservable().isBound());
}
@Override
public void unbind() {
Property<U> target = getTargetObservable();
if(target != null) {
target.unbind();
}
boundTo = null;
}
@Override
public void bindBidirectional(Property<U> other) {
Bindings.bindBidirectional(this, other);
}
@Override
public void unbindBidirectional(Property<U> other) {
Bindings.unbindBidirectional(this, other);
}
@Override
public Object getBean() {
return null;
}
@Override
public String getName() {
return null;
}
}