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

// Copyright 2023 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 androidx.annotation.NonNull;

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

/** Utilities for interactions with Suppliers. */
public class SupplierUtils {
    private SupplierUtils() {}

    private static class Barrier {
        private final ThreadUtils.ThreadChecker mThreadChecker = new ThreadUtils.ThreadChecker();
        private int mWaitingCount;
        private Runnable mCallback;

        void waitForAll(Runnable callback, Supplier... suppliers) {
            mThreadChecker.assertOnValidThread();
            assert mCallback == null;
            mCallback = callback;
            int waitingSupplierCount = 0;
            Callback<?> supplierCallback = (unused) -> onSupplierAvailable();
            for (Supplier<?> supplier : suppliers) {
                if (supplier.hasValue()) continue;

                waitingSupplierCount++;
                if (supplier instanceof ObservableSupplier) {
                    ObservableSupplier<?> observableSupplier = ((ObservableSupplier) supplier);
                    new OneShotCallback(observableSupplier, supplierCallback);
                } else if (supplier instanceof OneshotSupplier) {
                    ((OneshotSupplier) supplier).onAvailable(supplierCallback);
                } else if (supplier instanceof SyncOneshotSupplier) {
                    ((SyncOneshotSupplier) supplier).onAvailable(supplierCallback);
                } else {
                    assert false
                            : "Unexpected Supplier type that does not already have a value: "
                                    + supplier;
                }
            }
            mWaitingCount = waitingSupplierCount;
            notifyCallbackIfAppropriate();
        }

        private void onSupplierAvailable() {
            mThreadChecker.assertOnValidThread();
            mWaitingCount--;
            assert mWaitingCount >= 0;
            notifyCallbackIfAppropriate();
        }

        private void notifyCallbackIfAppropriate() {
            if (mWaitingCount != 0) return;
            if (mCallback == null) return;
            mCallback.run();
            mCallback = null;
        }
    }

    /**
     * Waits for all suppliers to have assigned values, and when that happens, notifies the
     * specified callback.
     *
     * <p>If all suppliers already have values, then the callback will be notified synchronously.
     *
     * <p>To prevent leaking objects, it is recommended to use {@link
     * org.chromium.base.CallbackController} for the {@link Runnable} callback.
     *
     * <p>Not thread safe. All passed in suppliers must be notified on the same thread this method
     * is called.
     *
     * @param callback The callback to be notified when all suppliers have values set.
     * @param suppliers The list of suppliers to check for values.
     */
    public static void waitForAll(@NonNull Runnable callback, Supplier... suppliers) {
        assert callback != null;
        new Barrier().waitForAll(callback, suppliers);
    }
}