chromium/base/android/java/src/org/chromium/base/supplier/ObservableSupplierImpl.java

// Copyright 2019 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.base.supplier;

import android.os.Handler;

import androidx.annotation.Nullable;

import org.chromium.base.Callback;
import org.chromium.base.ObserverList;
import org.chromium.base.ThreadUtils;
import org.chromium.base.ThreadUtils.ThreadChecker;

import java.util.Objects;

/**
 * Concrete implementation of {@link ObservableSupplier} to be used by classes owning the
 * ObservableSupplier and providing it as a dependency to others.
 *
 * <p>This class must only be accessed from a single thread.
 *
 * <pre>
 * To use:
 *   1. Create a new ObservableSupplierImpl<E> to pass as a dependency
 *   2. Call {@link #set(Object)} when the real object becomes available. {@link #set(Object)} may
 *      be called multiple times. Observers will be notified each time a new object is set.
 * </pre>
 *
 * @param <E> The type of the wrapped object.
 */
public class ObservableSupplierImpl<E> implements ObservableSupplier<E> {
    private final ThreadChecker mThreadChecker = new ThreadChecker();
    private final Handler mHandler = new Handler();

    private E mObject;
    private final ObserverList<Callback<E>> mObservers = new ObserverList<>();

    public ObservableSupplierImpl() {
        // Guard against creation on Instrumentation thread, since this is basically always a bug.
        assert !ThreadUtils.runningOnInstrumentationThread();
    }

    public ObservableSupplierImpl(E initialValue) {
        mObject = initialValue;
    }

    @Override
    public E addObserver(Callback<E> obs) {
        // ObserverList has its own ThreadChecker.
        mObservers.addObserver(obs);

        if (mObject != null) {
            final E currentObject = mObject;
            mHandler.post(
                    () -> {
                        if (mObject != currentObject || !mObservers.hasObserver(obs)) return;
                        obs.onResult(mObject);
                    });
        }

        return mObject;
    }

    @Override
    public void removeObserver(Callback<E> obs) {
        // ObserverList has its own ThreadChecker.
        mObservers.removeObserver(obs);
    }

    /**
     * Set the object supplied by this supplier. This will notify registered callbacks that the
     * dependency is available if the object changes. Object equality is used when deciding if the
     * object has changed, not reference equality.
     *
     * @param object The object to supply.
     */
    public void set(E object) {
        mThreadChecker.assertOnValidThread();
        if (Objects.equals(object, mObject)) {
            return;
        }

        mObject = object;

        for (Callback<E> observer : mObservers) {
            observer.onResult(mObject);
        }
    }

    @Override
    public @Nullable E get() {
        // Allow instrumentation thread access since tests often access variables for asserts.
        // https://crbug.com/1173814
        mThreadChecker.assertOnValidOrInstrumentationThread();
        return mObject;
    }

    /** Returns if there are any observers currently. */
    public boolean hasObservers() {
        // ObserverList has its own ThreadChecker.
        return !mObservers.isEmpty();
    }
}