chromium/chromecast/base/java/src/org/chromium/chromecast/base/Observer.java

// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.chromecast.base;

import java.util.function.BiFunction;
import java.util.function.Consumer;

/**
 * Interface representing the actions to perform when entering and exiting a state.
 *
 * The open() implementation is called when entering the state, and the Scope that it returns is
 * invoked when leaving the state. The side-effects of open() are like a constructor, and the
 * side-effects of the Scope's close() are like a destructor.
 *
 * @param <T> The argument type that the constructor takes.
 */
public interface Observer<T> {
    Scope open(T data);

    /**
     * Shorthand for making an Observer that only has side effects when the scope of data is opened.
     *
     * An Observer normally returns a Scope that defers a "cleanup" action until when the scope of
     * the given data is closed (i.e. the data is removed from the Observable). If you do not need
     * any cleanup operation for when data is removed, you can use this helper function to make an
     * Observer that only responds when data is added.
     *
     * Example:
     *
     *   Cell<String> src = new Cell<>("a");
     *   Subscription sub = src.subscribe(Observer.onOpen(System.out::println));  // Prints "a"
     *   src.set("b");  // Prints "b"
     *   src.set("c");  // Prints "c"
     *
     * @param <T> The type of the data to be observed.
     * @param consumer The action to be run when the scope of data is opened.
     * @return An Observer that applies the side effect when data is added.
     */
    static <T> Observer<T> onOpen(Consumer<? super T> consumer) {
        return data -> {
            consumer.accept(data);
            return Scope.NO_OP;
        };
    }

    /**
     * Shorthand for making an Observer that only has side effects when the scope of data is closed.
     *
     * An Observer normally has effects that are executed immediately when the scope of data is
     * opened (i.e. data is added to the Observable). If you do not need to execute any operation
     * when data is added, but do need to register a "cleanup" action for the data, you can use this
     * helper function to make an Observer that only responds when data is removed.
     *
     * Example:
     *
     *   Cell<String> src = new Cell<>("a");
     *   Subscription sub = src.subscribe(Observer.onClose(System.out::println));  // Does nothing.
     *   src.set("b");  // Prints "a", because it was removed when replaced by "b"
     *   src.set("c");  // Prints "b", because it was removed when replaced by "c"
     *
     * @param <T> The type of the data to be observed.
     * @param consumer The action to be run when the scope of data is closed.
     * @return An Observer that applies side effect when data is removed.
     */
    static <T> Observer<T> onClose(Consumer<? super T> consumer) {
        return data -> () -> consumer.accept(data);
    }

    /**
     * Adapts an Observer-like function that takes two arguments into a true Observer that takes a
     * Both object.
     *
     * For example, one can refactor the following:
     *
     *   observableA.and(observableB).subscribe((Both<A, B> data) -> {
     *       A a = data.first;
     *       B b = data.second;
     *   });
     *
     * ... into this:
     *
     *   observableA.and(observableB).subscribe(Observers.both((A a, B b) -> ...));
     *
     * @param <A> The type of the first argument (and the first item in the Both).
     * @param <B> The type of the second argument (and the second item in the Both).
     * @param f The function to be converted into an Observer.
     * @return An Observer that can be subscribed to an Observable<Both<A, B>>.
     */
    static <A, B> Observer<Both<A, B>> both(BiFunction<? super A, ? super B, ? extends Scope> f) {
        return data -> f.apply(data.first, data.second);
    }
}