org.reactfx.value.Val Maven / Gradle / Ivy
Show all versions of reactfx Show documentation
package org.reactfx.value;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.property.Property;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import org.reactfx.EventStream;
import org.reactfx.EventStreamBase;
import org.reactfx.Subscription;
import org.reactfx.util.HexaFunction;
import org.reactfx.util.PentaFunction;
import org.reactfx.util.TetraFunction;
import org.reactfx.util.TriFunction;
import org.reactfx.util.WrapperBase;
/**
* Adds more operations to {@link ObservableValue}.
*/
public interface Val extends ObservableValue {
/* **************** *
* Abstract methods *
* **************** */
void addInvalidationObserver(Consumer oldValueObserver);
void removeInvalidationObserver(Consumer oldValueObserver);
/* *************** *
* Default methods *
* *************** */
default Subscription observeInvalidations(
Consumer oldValueObserver) {
addInvalidationObserver(oldValueObserver);
return () -> removeInvalidationObserver(oldValueObserver);
}
default Subscription pin() {
return observeInvalidations(oldVal -> {});
}
@Override
default void addListener(InvalidationListener listener) {
addInvalidationObserver(new InvalidationListenerWrapper<>(this, listener));
}
@Override
default void removeListener(InvalidationListener listener) {
removeInvalidationObserver(new InvalidationListenerWrapper<>(this, listener));
}
@Override
default void addListener(ChangeListener listener) {
addInvalidationObserver(new ChangeListenerWrapper<>(this, listener));
}
@Override
default void removeListener(ChangeListener listener) {
removeInvalidationObserver(new ChangeListenerWrapper<>(this, listener));
}
/**
* Adds a change listener and returns a Subscription that can be
* used to remove that listener.
*/
default Subscription observeChanges(ChangeListener listener) {
return observeInvalidations(new ChangeListenerWrapper<>(this, listener));
}
/**
* Returns a stream of invalidated values, which emits the invalidated value
* (i.e. the old value) on each invalidation of this observable value.
*/
default EventStream invalidations() {
return new EventStreamBase() {
@Override
protected Subscription observeInputs() {
return observeInvalidations(this::emit);
}
};
}
/**
* Checks whether this {@linkplain Val} holds a (non-null) value.
* @return {@code true} if this {@linkplain Val} 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 {@linkplain Val} holds a (non-null)
* value.
* @param f function to invoke on the value currently held by this
* {@linkplain Val}.
*/
default void ifPresent(Consumer f) {
T val = getValue();
if(val != null) {
f.accept(val);
}
}
/**
* Returns the value currently held by this {@linkplain Val}.
* @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 {@linkplain Val}. If this
* {@linkplain Val} is empty, {@code defaultValue} is returned instead.
* @param defaultValue value to return if there is no value present in
* this {@linkplain Val}.
*/
default T getOrElse(T defaultValue) {
T res = getValue();
if(res != null) {
return res;
} else {
return defaultValue;
}
}
/**
* Like {@link #getOrElse(Object)}, except the default value is computed
* by {@code defaultSupplier} only when necessary.
* @param defaultSupplier computation to produce default value, if this
* {@linkplain Val} is empty.
*/
default T getOrSupply(Supplier defaultSupplier) {
T res = getValue();
if(res != null) {
return res;
} else {
return defaultSupplier.get();
}
}
/**
* Returns an {@code Optional} describing the value currently held by this
* {@linkplain Val}, or and empty {@code Optional} if this {@linkplain Val}
* is empty.
*/
default Optional getOpt() {
return Optional.ofNullable(getValue());
}
/**
* Returns a new {@linkplain Val} that holds the value held by this
* {@linkplain Val}, or {@code other} when this {@linkplain Val} is empty.
*/
default Val orElseConst(T other) {
return orElseConst(this, other);
}
/**
* Returns a new {@linkplain Val} that holds the value held by this
* {@linkplain Val}, or the value held by {@code other} when this
* {@linkplain Val} is empty.
*/
default Val orElse(ObservableValue other) {
return orElse(this, other);
}
/**
* Returns a new {@linkplain Val} that holds the same value
* as this {@linkplain Val} when the value satisfies the predicate
* and is empty when this {@linkplain Val} is empty or its value
* does not satisfy the given predicate.
*/
default Val filter(Predicate p) {
return filter(this, p);
}
/**
* Returns a new {@linkplain Val} that holds a mapping of the value held by
* this {@linkplain Val}, and is empty when this {@linkplain Val} is empty.
* @param f function to map the value held by this {@linkplain Val}.
*/
default Val map(Function f) {
return map(this, f);
}
/**
* Like {@link #map(Function)}, but also allows dynamically changing
* map function.
*/
default Val mapDynamic(
ObservableValue> f) {
return mapDynamic(this, f);
}
/**
* Returns a new {@linkplain Val} that, when this {@linkplain Val} holds
* value {@code x}, holds the value held by {@code f(x)}, and is empty
* when this {@linkplain Val} is empty.
*/
default Val flatMap(
Function> f) {
return 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.
*
* As the value of this {@linkplain Val} 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.
*
*
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()}.
*/
default Var selectVar(
Function> f) {
return selectVar(this, f);
}
default Var selectVar(
Function> f,
U resetToOnUnbind) {
return selectVar(this, f, resetToOnUnbind);
}
default SuspendableVal suspendable() {
return suspendable(this);
}
/* ************** *
* Static methods *
* ************** */
/**
* Returns a {@linkplain Val} wrapper around {@linkplain ObservableValue}.
* Note that one rarely needs to use this method, because most of the time
* one can use an appropriate static method directly to get the desired
* result. For example, instead of
*
*
* {@code
* Val.wrap(obs).orElse(other)
* }
*
*
* one can write
*
*
* {@code
* Val.orElse(obs, other)
* }
*
*
* However, an explicit wrapper is needed if one needs to access the methods
* {@link #observeInvalidations(Consumer)} or {@link #invalidations()}, that
* is methods that observe the invalidated value, since there is no way
* these can be implemented as static helper methods.
*/
static Val wrap(ObservableValue obs) {
return obs instanceof Val
? (Val) obs
: new ValWrapper<>(obs);
}
static Subscription observeChanges(
ObservableValue obs,
ChangeListener listener) {
if(obs instanceof Val) {
return ((Val) obs).observeChanges(listener);
} else {
obs.addListener(listener);
return () -> obs.removeListener(listener);
}
}
static Subscription observeInvalidations(
ObservableValue obs,
InvalidationListener listener) {
obs.addListener(listener);
return () -> obs.removeListener(listener);
}
static Val orElseConst(ObservableValue src, T other) {
return new OrElseConst<>(src, other);
}
static Val orElse(
ObservableValue src,
ObservableValue other) {
return new OrElse<>(src, other);
}
static Val filter(
ObservableValue src,
Predicate p) {
return new FilteredVal<>(src, p);
}
static Val map(
ObservableValue src,
Function f) {
return new MappedVal<>(src, f);
}
static Val mapDynamic(
ObservableValue src,
ObservableValue> f) {
return new DynamicallyMappedVal(src, f);
}
static Val flatMap(
ObservableValue src,
Function> f) {
return new FlatMappedVal<>(src, f);
}
static Var selectVar(
ObservableValue src,
Function> f) {
return new FlatMappedVar<>(src, f);
}
static Var selectVar(
ObservableValue src,
Function> f,
U resetToOnUnbind) {
return new FlatMappedVar<>(src, f, resetToOnUnbind);
}
static SuspendableVal suspendable(ObservableValue obs) {
if(obs instanceof SuspendableVal) {
return (SuspendableVal) obs;
} else {
Val val = obs instanceof Val
? (Val) obs
: new ValWrapper<>(obs);
return new SuspendableValWrapper<>(val);
}
}
static Val combine(
ObservableValue src1,
ObservableValue src2,
BiFunction f) {
return create(
() -> {
if(src1.getValue() != null && src2.getValue() != null) {
return f.apply(src1.getValue(), src2.getValue());
} else {
return null;
}
},
src1, src2);
}
static Val combine(
ObservableValue src1,
ObservableValue src2,
ObservableValue src3,
TriFunction f) {
return create(
() -> {
if(src1.getValue() != null &&
src2.getValue() != null &&
src3.getValue() != null) {
return f.apply(
src1.getValue(), src2.getValue(), src3.getValue());
} else {
return null;
}
},
src1, src3, src3);
}
static Val combine(
ObservableValue src1,
ObservableValue src2,
ObservableValue src3,
ObservableValue src4,
TetraFunction f) {
return create(
() -> {
if(src1.getValue() != null &&
src2.getValue() != null &&
src3.getValue() != null &&
src4.getValue() != null) {
return f.apply(
src1.getValue(), src2.getValue(),
src3.getValue(), src4.getValue());
} else {
return null;
}
},
src1, src2, src3, src4);
}
static Val combine(
ObservableValue src1,
ObservableValue src2,
ObservableValue src3,
ObservableValue src4,
ObservableValue src5,
PentaFunction f) {
return create(
() -> {
if(src1.getValue() != null &&
src2.getValue() != null &&
src3.getValue() != null &&
src4.getValue() != null &&
src5.getValue() != null) {
return f.apply(
src1.getValue(), src2.getValue(), src3.getValue(),
src4.getValue(), src5.getValue());
} else {
return null;
}
},
src1, src2, src3, src4, src5);
}
static Val combine(
ObservableValue src1,
ObservableValue src2,
ObservableValue src3,
ObservableValue src4,
ObservableValue src5,
ObservableValue src6,
HexaFunction f) {
return create(
() -> {
if(src1.getValue() != null &&
src2.getValue() != null &&
src3.getValue() != null &&
src4.getValue() != null &&
src5.getValue() != null &&
src6.getValue() != null) {
return f.apply(
src1.getValue(), src2.getValue(), src3.getValue(),
src4.getValue(), src5.getValue(), src6.getValue());
} else {
return null;
}
},
src1, src2, src3, src4, src5, src6);
}
static Val create(
Supplier computeValue,
Observable... dependencies) {
return new ValBase() {
@Override
protected Subscription connect() {
InvalidationListener listener = obs -> invalidate();
for(Observable dep: dependencies) {
dep.addListener(listener);
}
return () -> {
for(Observable dep: dependencies) {
dep.removeListener(listener);
}
};
}
@Override
protected T computeValue() {
return computeValue.get();
}
};
}
}
class InvalidationListenerWrapper
extends WrapperBase
implements Consumer {
private final ObservableValue obs;
public InvalidationListenerWrapper(
ObservableValue obs,
InvalidationListener listener) {
super(listener);
this.obs = obs;
}
@Override
public void accept(T oldValue) {
getWrappedValue().invalidated(obs);
}
}
class ChangeListenerWrapper
extends WrapperBase>
implements Consumer {
private final ObservableValue obs;
public ChangeListenerWrapper(
ObservableValue obs,
ChangeListener listener) {
super(listener);
this.obs = obs;
}
@Override
public void accept(T oldValue) {
T newValue = obs.getValue();
if(!Objects.equals(oldValue, newValue)) {
getWrappedValue().changed(obs, oldValue, newValue);
}
}
}