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

org.reactfx.EventStreams Maven / Gradle / Ivy

There is a newer version: 2.0-M5
Show newest version
package org.reactfx;

import static org.reactfx.util.Tuples.*;

import java.time.Duration;
import java.util.Collection;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

import javafx.animation.AnimationTimer;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.ListChangeListener;
import javafx.collections.MapChangeListener;
import javafx.collections.ObservableList;
import javafx.collections.ObservableMap;
import javafx.collections.ObservableSet;
import javafx.collections.SetChangeListener;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.event.EventType;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.MenuItem;

import org.reactfx.collection.ListModification;
import org.reactfx.collection.LiveList;
import org.reactfx.util.FxTimer;
import org.reactfx.util.Timer;
import org.reactfx.util.Tuple2;
import org.reactfx.util.Tuple3;
import org.reactfx.util.Tuple4;
import org.reactfx.util.Tuple5;
import org.reactfx.util.Tuple6;

public class EventStreams {

    private static final EventStream NEVER = new EventStream() {

        @Override
        public Subscription subscribe(Consumer subscriber) {
            return Subscription.EMPTY;
        }
    };

    /**
     * Returns an event stream that never emits any value.
     */
    @SuppressWarnings("unchecked")
    public static  EventStream never() {
        return (EventStream) NEVER;
    }

    /**
     * Creates an event stream that emits an impulse on every invalidation
     * of the given observable.
     */
    public static EventStream invalidationsOf(Observable observable) {
        return new EventStreamBase() {
            @Override
            protected Subscription observeInputs() {
                InvalidationListener listener = obs -> emit(null);
                observable.addListener(listener);
                return () -> observable.removeListener(listener);
            }
        };
    }

    /**
     * Creates an event stream that emits the given observable immediately for
     * every subscriber and re-emits it on every subsequent invalidation of the
     * observable.
     */
    public static 
    EventStream repeatOnInvalidation(O observable) {
        return new EventStreamBase() {
            @Override
            protected Subscription observeInputs() {
                InvalidationListener listener = obs -> emit(observable);
                observable.addListener(listener);
                return () -> observable.removeListener(listener);
            }

            @Override
            protected void newObserver(Consumer subscriber) {
                subscriber.accept(observable);
            }
        };
    }

    /**
     * Creates an event stream that emits the value of the given
     * {@code ObservableValue} immediately for every subscriber and then on
     * every change.
     */
    public static  EventStream valuesOf(ObservableValue observable) {
        return new EventStreamBase() {
            @Override
            protected Subscription observeInputs() {
                ChangeListener listener = (obs, old, val) -> emit(val);
                observable.addListener(listener);
                return () -> observable.removeListener(listener);
            }

            @Override
            protected void newObserver(Consumer subscriber) {
                subscriber.accept(observable.getValue());
            }
        };
    }

    public static  EventStream nonNullValuesOf(ObservableValue observable) {
        return new EventStreamBase() {
            @Override
            protected Subscription observeInputs() {
                ChangeListener listener = (obs, old, val) -> {
                    if(val != null) {
                        emit(val);
                    }
                };
                observable.addListener(listener);
                return () -> observable.removeListener(listener);
            }

            @Override
            protected void newObserver(Consumer subscriber) {
                T val = observable.getValue();
                if(val != null) {
                    subscriber.accept(val);
                }
            }
        };
    }

    public static  EventStream> changesOf(ObservableValue observable) {
        return new EventStreamBase>() {
            @Override
            protected Subscription observeInputs() {
                ChangeListener listener = (obs, old, val) -> emit(new Change<>(old, val));
                observable.addListener(listener);
                return () -> observable.removeListener(listener);
            }
        };
    }

    /**
     * @see LiveList#changesOf(ObservableList)
     */
    public static  EventStream> changesOf(ObservableList list) {
        return new EventStreamBase>() {
            @Override
            protected Subscription observeInputs() {
                ListChangeListener listener = c -> emit(c);
                list.addListener(listener);
                return () -> list.removeListener(listener);
            }
        };
    }

    /**
     * Use only when the subscriber does not cause {@code list} modification
     * of the underlying list.
     */
    public static  EventStream> simpleChangesOf(ObservableList list) {
        return new EventStreamBase>() {
            @Override
            protected Subscription observeInputs() {
                return LiveList.observeChanges(list, c ->  {
                    for(ListModification mod: c) {
                        emit(mod);
                    }
                });
            }
        };
    }

    public static  EventStream> changesOf(ObservableSet set) {
        return new EventStreamBase>() {
            @Override
            protected Subscription observeInputs() {
                SetChangeListener listener = c -> emit(c);
                set.addListener(listener);
                return () -> set.removeListener(listener);
            }
        };
    }

    public static  EventStream> changesOf(ObservableMap map) {
        return new EventStreamBase>() {
            @Override
            protected Subscription observeInputs() {
                MapChangeListener listener = c -> emit(c);
                map.addListener(listener);
                return () -> map.removeListener(listener);
            }
        };
    }

    public static  & Observable> EventStream sizeOf(C collection) {
        return create(() -> collection.size(), collection);
    }

    public static EventStream sizeOf(ObservableMap map) {
        return create(() -> map.size(), map);
    }

    private static  EventStream create(Supplier computeValue, Observable... dependencies) {
        return new EventStreamBase() {
            private T previousValue;

            @Override
            protected Subscription observeInputs() {
                InvalidationListener listener = obs -> {
                    T value = computeValue.get();
                    if(value != previousValue) {
                        previousValue = value;
                        emit(value);
                    }
                };
                for(Observable dep: dependencies) {
                    dep.addListener(listener);
                }
                previousValue = computeValue.get();

                return () -> {
                    for(Observable dep: dependencies) {
                        dep.removeListener(listener);
                    }
                };
            }

            @Override
            protected void newObserver(Consumer subscriber) {
                subscriber.accept(previousValue);
            }
        };
    }

    public static  EventStream eventsOf(
            Node node, EventType eventType) {
        return new EventStreamBase() {
            @Override
            protected Subscription observeInputs() {
                EventHandler handler = this::emit;
                node.addEventHandler(eventType, handler);
                return () -> node.removeEventHandler(eventType, handler);
            }
        };
    }

    public static  EventStream eventsOf(
            Scene scene, EventType eventType) {
        return new EventStreamBase() {
            @Override
            protected Subscription observeInputs() {
                EventHandler handler = this::emit;
                scene.addEventHandler(eventType, handler);
                return () -> scene.removeEventHandler(eventType, handler);
            }
        };
    }

    public static  EventStream eventsOf(
            MenuItem menuItem, EventType eventType) {
        return new EventStreamBase() {
            @Override
            protected Subscription observeInputs() {
                EventHandler handler = this::emit;
                menuItem.addEventHandler(eventType, handler);
                return () -> menuItem.removeEventHandler(eventType, handler);
            }
        };
    }

    /**
     * Returns an event stream that emits periodic ticks. The returned
     * stream may only be used on the JavaFX application thread.
     *
     * 

As with all lazily bound streams, ticks are emitted only when there * is at least one subscriber to the returned stream. This means that to * release associated resources, it suffices to unsubscribe from the * returned stream. */ public static EventStream ticks(Duration interval) { return new EventStreamBase() { private final Timer timer = FxTimer.createPeriodic( interval, () -> emit(null)); @Override protected Subscription observeInputs() { timer.restart(); return timer::stop; } }; } /** * Returns an event stream that emits periodic ticks on the given * {@code eventThreadExecutor}. The returned stream may only be used from * that executor's thread. * *

As with all lazily bound streams, ticks are emitted only when there * is at least one subscriber to the returned stream. This means that to * release associated resources, it suffices to unsubscribe from the * returned stream. * * @param scheduler scheduler used to schedule periodic emissions. * @param eventThreadExecutor single-thread executor used to emit the ticks. */ public static EventStream ticks( Duration interval, ScheduledExecutorService scheduler, Executor eventThreadExecutor) { return new EventStreamBase() { private final Timer timer = ScheduledExecutorServiceTimer.createPeriodic( interval, () -> emit(null), scheduler, eventThreadExecutor); @Override protected Subscription observeInputs() { timer.restart(); return timer::stop; } }; } /** * Returns an event stream that emits a timestamp of the current frame in * nanoseconds on every frame. The timestamp has the same meaning as the * argument of the {@link AnimationTimer#handle(long)} method. */ public static EventStream animationTicks() { return new EventStreamBase() { private final AnimationTimer timer = new AnimationTimer() { @Override public void handle(long now) { emit(now); } }; @Override protected Subscription observeInputs() { timer.start(); return timer::stop; } }; } /** * Returns an event stream that emits all the events emitted from any of * the {@code inputs}. The event type of the returned stream is the nearest * common super-type of all the {@code inputs}. * * @see EventStream#or(EventStream) */ @SafeVarargs public static EventStream merge(EventStream... inputs) { return new EventStreamBase() { @Override protected Subscription observeInputs() { return Subscription.multi(i -> i.subscribe(this::emit), inputs); } }; } /** * Returns an event stream that emits all the events emitted from any of * the event streams in the given observable set. When an event stream is * added to the set, the returned stream will start emitting its events. * When an event stream is removed from the set, its events will no longer * be emitted from the returned stream. */ public static EventStream merge( ObservableSet> set) { return new EventStreamBase() { @Override protected Subscription observeInputs() { return Subscription.dynamic(set, s -> s.subscribe(this::emit)); } }; } /** * A more general version of {@link #merge(ObservableSet)} for a set of * arbitrary element type and a function to obtain an event stream from * the element. * @param set observable set of elements * @param f function to obtain an event stream from an element */ public static EventStream merge( ObservableSet set, Function> f) { return new EventStreamBase() { @Override protected Subscription observeInputs() { return Subscription.dynamic( set, t -> f.apply(t).subscribe(this::emit)); } }; } public static EventStream> zip(EventStream srcA, EventStream srcB) { return new EventStreamBase>() { Pocket pocketA = new ExclusivePocket<>(); Pocket pocketB = new ExclusivePocket<>(); @Override protected Subscription observeInputs() { pocketA.clear(); pocketB.clear(); return Subscription.multi( srcA.subscribe(a -> { pocketA.set(a); tryEmit(); }), srcB.subscribe(b -> { pocketB.set(b); tryEmit(); })); } protected void tryEmit() { if(pocketA.hasValue() && pocketB.hasValue()) { emit(t(pocketA.getAndClear(), pocketB.getAndClear())); } } }; } public static EventStream> zip(EventStream srcA, EventStream srcB, EventStream srcC) { return new EventStreamBase>() { Pocket pocketA = new ExclusivePocket<>(); Pocket pocketB = new ExclusivePocket<>(); Pocket pocketC = new ExclusivePocket<>(); @Override protected Subscription observeInputs() { pocketA.clear(); pocketB.clear(); pocketC.clear(); return Subscription.multi( srcA.subscribe(a -> { pocketA.set(a); tryEmit(); }), srcB.subscribe(b -> { pocketB.set(b); tryEmit(); }), srcC.subscribe(c -> { pocketC.set(c); tryEmit(); })); } protected void tryEmit() { if(pocketA.hasValue() && pocketB.hasValue() && pocketC.hasValue()) { emit(t(pocketA.getAndClear(), pocketB.getAndClear(), pocketC.getAndClear())); } } }; } public static EventStream> combine( EventStream srcA, EventStream srcB) { return new EventStreamBase>() { Pocket pocketA = new Pocket<>(); Pocket pocketB = new Pocket<>(); @Override protected Subscription observeInputs() { pocketA.clear(); pocketB.clear(); return Subscription.multi( srcA.subscribe(a -> { pocketA.set(a); tryEmit(); }), srcB.subscribe(b -> { pocketB.set(b); tryEmit(); })); } void tryEmit() { if(pocketA.hasValue() && pocketB.hasValue()) { emit(t(pocketA.get(), pocketB.get())); } } }; } public static EventStream> combine( EventStream srcA, EventStream srcB, EventStream srcC) { return new EventStreamBase>() { Pocket pocketA = new Pocket<>(); Pocket pocketB = new Pocket<>(); Pocket pocketC = new Pocket<>(); @Override protected Subscription observeInputs() { pocketA.clear(); pocketB.clear(); pocketC.clear(); return Subscription.multi( srcA.subscribe(a -> { pocketA.set(a); tryEmit(); }), srcB.subscribe(b -> { pocketB.set(b); tryEmit(); }), srcC.subscribe(c -> { pocketC.set(c); tryEmit(); })); } void tryEmit() { if(pocketA.hasValue() && pocketB.hasValue() && pocketC.hasValue()) { emit(t(pocketA.get(), pocketB.get(), pocketC.get())); } } }; } public static EventStream> combine( EventStream srcA, EventStream srcB, EventStream srcC, EventStream srcD) { return new EventStreamBase>() { Pocket pocketA = new Pocket<>(); Pocket pocketB = new Pocket<>(); Pocket pocketC = new Pocket<>(); Pocket pocketD = new Pocket<>(); @Override protected Subscription observeInputs() { pocketA.clear(); pocketB.clear(); pocketC.clear(); pocketD.clear(); return Subscription.multi( srcA.subscribe(a -> { pocketA.set(a); tryEmit(); }), srcB.subscribe(b -> { pocketB.set(b); tryEmit(); }), srcC.subscribe(c -> { pocketC.set(c); tryEmit(); }), srcD.subscribe(d -> { pocketD.set(d); tryEmit(); })); } void tryEmit() { if(pocketA.hasValue() && pocketB.hasValue() && pocketC.hasValue() && pocketD.hasValue()) { emit(t(pocketA.get(), pocketB.get(), pocketC.get(), pocketD.get())); } } }; } public static EventStream> combine( EventStream srcA, EventStream srcB, EventStream srcC, EventStream srcD, EventStream srcE) { return new EventStreamBase>() { Pocket pocketA = new Pocket<>(); Pocket pocketB = new Pocket<>(); Pocket pocketC = new Pocket<>(); Pocket pocketD = new Pocket<>(); Pocket pocketE = new Pocket<>(); @Override protected Subscription observeInputs() { pocketA.clear(); pocketB.clear(); pocketC.clear(); pocketD.clear(); pocketE.clear(); return Subscription.multi( srcA.subscribe(a -> { pocketA.set(a); tryEmit(); }), srcB.subscribe(b -> { pocketB.set(b); tryEmit(); }), srcC.subscribe(c -> { pocketC.set(c); tryEmit(); }), srcD.subscribe(d -> { pocketD.set(d); tryEmit(); }), srcE.subscribe(e -> { pocketE.set(e); tryEmit(); })); } void tryEmit() { if(pocketA.hasValue() && pocketB.hasValue() && pocketC.hasValue() && pocketD.hasValue() && pocketE.hasValue()) { emit(t(pocketA.get(), pocketB.get(), pocketC.get(), pocketD.get(), pocketE.get())); } } }; } public static EventStream> combine( EventStream srcA, EventStream srcB, EventStream srcC, EventStream srcD, EventStream srcE, EventStream srcF) { return new EventStreamBase>() { Pocket pocketA = new Pocket<>(); Pocket pocketB = new Pocket<>(); Pocket pocketC = new Pocket<>(); Pocket pocketD = new Pocket<>(); Pocket pocketE = new Pocket<>(); Pocket pocketF = new Pocket<>(); @Override protected Subscription observeInputs() { pocketA.clear(); pocketB.clear(); pocketC.clear(); pocketD.clear(); pocketE.clear(); pocketF.clear(); return Subscription.multi( srcA.subscribe(a -> { pocketA.set(a); tryEmit(); }), srcB.subscribe(b -> { pocketB.set(b); tryEmit(); }), srcC.subscribe(c -> { pocketC.set(c); tryEmit(); }), srcD.subscribe(d -> { pocketD.set(d); tryEmit(); }), srcE.subscribe(e -> { pocketE.set(e); tryEmit(); }), srcF.subscribe(f -> { pocketF.set(f); tryEmit(); })); } void tryEmit() { if(pocketA.hasValue() && pocketB.hasValue() && pocketC.hasValue() && pocketD.hasValue() && pocketE.hasValue() && pocketF.hasValue()) { emit(t(pocketA.get(), pocketB.get(), pocketC.get(), pocketD.get(), pocketE.get(), pocketF.get())); } } }; } private static class Pocket { private boolean hasValue = false; private T value = null; public boolean hasValue() { return hasValue; } public void set(T value) { this.value = value; hasValue = true; } public T get() { return value; } public void clear() { hasValue = false; value = null; } public T getAndClear() { T res = get(); clear(); return res; } } private static class ExclusivePocket extends Pocket { @Override public final void set(T a) { if(hasValue()) { throw new IllegalStateException("Value arrived out of order: " + a); } else { super.set(a); } }; } }