package org.fxmisc.easybind.monadic;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import javafx.beans.InvalidationListener;
import javafx.beans.property.Property;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableObjectValue;
import javafx.beans.value.ObservableValue;
import org.fxmisc.easybind.EasyBind;
import org.fxmisc.easybind.Subscription;
import org.fxmisc.easybind.select.SelectBuilder;
/**
* Adds monadic operations to {@link ObservableValue}.
*/
public interface MonadicObservableValue<T> extends ObservableObjectValue<T> {
/**
* Checks whether this ObservableValue holds a (non-null) value.
* @return {@code true} if this ObservableValue holds a (non-null) value,
* {@code false} otherwise.
*/
default boolean isPresent() {
return getValue() != null;
}
/**
* Inverse of {@link #isPresent()}.
*/
default boolean isEmpty() {
return getValue() == null;
}
/**
* Invokes the given function if this ObservableValue holds a (non-null)
* value.
* @param f function to invoke on the value currently held by this
* ObservableValue.
*/
default void ifPresent(Consumer<? super T> f) {
T val = getValue();
if(val != null) {
f.accept(val);
}
}
/**
* Returns the value currently held by this ObservableValue.
* @throws NoSuchElementException if there is no value present.
*/
default T getOrThrow() {
T res = getValue();
if(res != null) {
return res;
} else {
throw new NoSuchElementException();
}
}
/**
* Returns the value currently held by this ObservableValue.
* If this ObservableValue is empty, {@code other} is returned instead.
* @param other value to return if there is no value present in this
* ObservableValue.
*/
default T getOrElse(T other) {
T res = getValue();
if(res != null) {
return res;
} else {
return other;
}
}
/**
* Returns an {@code Optional} describing the value currently held by this
* ObservableValue, or and empty {@code Optional} if this ObservableValue
* is empty.
*/
default Optional<T> getOpt() {
return Optional.ofNullable(getValue());
}
/**
* Returns a new ObservableValue that holds the value held by this
* ObservableValue, or {@code other} when this ObservableValue is empty.
*/
default MonadicBinding<T> orElse(T other) {
return EasyBind.orElse(this, other);
}
/**
* Returns a new ObservableValue that holds the value held by this
* ObservableValue, or the value held by {@code other} when this
* ObservableValue is empty.
*/
default MonadicBinding<T> orElse(ObservableValue<T> other) {
return EasyBind.orElse(this, other);
}
/**
* Returns a new ObservableValue that holds the same value
* as this ObservableValue when the value satisfies the predicate
* and is empty when this ObservableValue is empty or its value
* does not satisfy the given predicate.
*/
default MonadicBinding<T> filter(Predicate<? super T> p) {
return EasyBind.filter(this, p);
}
/**
* Returns a new ObservableValue that holds a mapping of the value held
* by this ObservableValue, and is empty when this ObservableValue is empty.
* @param f function to map the value held by this ObservableValue.
*/
default <U> MonadicBinding<U> map(Function<? super T, ? extends U> f) {
return EasyBind.map(this, f);
}
/**
* Returns a new ObservableValue that, when this ObservableValue holds
* value {@code x}, holds the value held by {@code f(x)}, and is empty
* when this ObservableValue is empty.
*/
default <U> MonadicBinding<U> flatMap(
Function<? super T, ? extends ObservableValue<U>> f) {
return EasyBind.flatMap(this, f);
}
/**
* Similar to {@link #flatMap(Function)}, except the returned Binding is
* also a Property. This means you can call {@code setValue()} and
* {@code bind()} methods on the returned value, which delegate to the
* currently selected Property.
*
* <p>As the value of this ObservableValue changes, so does the selected
* Property. When the Property returned from this method is bound, as the
* selected Property changes, the previously selected property is unbound
* and the newly selected property is bound.
*
* <p>Note that if the currently selected property is {@code null}, then
* calling {@code getValue()} on the returned value will return {@code null}
* regardless of any prior call to {@code setValue()} or {@code bind()}.
*
* <p>Note that you need to retain a reference to the returned value to
* prevent it from being garbage collected.
*/
default <U> PropertyBinding<U> selectProperty(
Function<? super T, ? extends Property<U>> f) {
return EasyBind.selectProperty(this, f);
}
/**
* Starts a selection chain. A selection chain is just a more efficient
* equivalent to a chain of flatMaps.
*/
default <U> SelectBuilder<U> select(Function<? super T, ObservableValue<U>> selector) {
return SelectBuilder.startAt(this).select(selector);
}
/**
* Adds an invalidation listener and returns a Subscription that can be
* used to remove that listener.
*
* <pre>
* {@code
* Subscription s = observable.subscribe(obs -> doSomething());
*
* // later
* s.unsubscribe();
* }</pre>
*
* is equivalent to
*
* <pre>
* {@code
* InvalidationListener l = obs -> doSomething();
* observable.addListener(l);
*
* // later
* observable.removeListener();
* }</pre>
*/
default Subscription subscribe(InvalidationListener listener) {
addListener(listener);
return () -> removeListener(listener);
}
/**
* Adds a change listener and returns a Subscription that can be
* used to remove that listener. See the example at
* {@link #subscribe(InvalidationListener)}.
*/
default Subscription subscribe(ChangeListener<? super T> listener) {
addListener(listener);
return () -> removeListener(listener);
}
}