All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.reactfx.value.Val Maven / Gradle / Ivy

There is a newer version: 2.0-M5
Show newest version
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); } } }