1 // Copyright 2023 The Chromium Authors 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.base.supplier; 6 7 import androidx.annotation.Nullable; 8 9 import org.chromium.base.Callback; 10 import org.chromium.base.Promise; 11 import org.chromium.base.ThreadUtils; 12 13 /** 14 * Abstract implementation of {@link LazySupplier} to be used by classes providing it as a 15 * dependency to others. A call to {@link LazyOneshotSupplier#get()} will attempt to set the 16 * supplied object via {@link LazyOneshotSupplier#doSet()}. Additionally, {@link 17 * LazyOneshotSupplier#onAvailable(Callback<T>)} will not call {@link LazyOneshotSupplier#get()} 18 * unless it already has a value to prevent eager initialization. The supplied value can be null, 19 * {@link LazyOneshotSupplier#hasValue} should be used to differentiate between un/set states. 20 * 21 * <p>If eager initialization in response to {@link LazyOneshotSupplier#onAvailable(Callback<T>)} is 22 * required then a call to {@link LazyOneshotSupplier#get()} can be made just before attaching the 23 * callback. 24 * 25 * <p>Instances of this class must only be accessed from the thread they were created on. 26 * 27 * <p>To use: 28 * 29 * <ol> 30 * <li>Create a new {@code LazyOneshotSupplier<T>} to pass as a dependency. 31 * <li>Override {@link #doSet()} to invoke {@link #set(T)}. This will be invoked when {@link 32 * #get()} is invoked if {@link #hasValue()} returns false. Note that invoking {@link 33 * #doSet()} does not have to invoke {@link #set(T)} if there is reason not to such as 34 * awaiting an async dependency. However, if this is the case clients of the supplier need to 35 * be careful to properly understand the initialization lifecycle. 36 * </ol> 37 * 38 * @param <T> The type of the wrapped object. 39 */ 40 public abstract class LazyOneshotSupplierImpl<T> implements LazyOneshotSupplier<T> { 41 private final Promise<T> mPromise = new Promise<>(); 42 private final ThreadUtils.ThreadChecker mThreadChecker = new ThreadUtils.ThreadChecker(); 43 44 private boolean mDoSetCalled; 45 46 /** 47 * Lazily invokes the callback the first time {@link #set(T)} is invoked or immediately if 48 * already available. 49 */ 50 @Override onAvailable(Callback<T> callback)51 public void onAvailable(Callback<T> callback) { 52 mThreadChecker.assertOnValidThread(); 53 mPromise.then(callback); 54 } 55 56 /** 57 * Return the value of the supplier. Calling this the first time will initialize the value in 58 * the supplier via {@link #doSet()}. 59 * 60 * @return the value that was provided in {@link #set(T)} or null. 61 */ 62 @Override get()63 public @Nullable T get() { 64 mThreadChecker.assertOnValidThread(); 65 if (!hasValue()) { 66 tryDoSet(); 67 } 68 return hasValue() ? mPromise.getResult() : null; 69 } 70 71 /** Returns whether a value is set in the supplier. */ 72 @Override hasValue()73 public boolean hasValue() { 74 return mPromise.isFulfilled(); 75 } 76 77 /** 78 * Sets the value upon first {@link #get()}. Implementers should override this to invoke {@link 79 * #set(T)}. 80 */ doSet()81 public abstract void doSet(); 82 83 /** 84 * Set the object supplied by this supplier. This will notify registered callbacks that the 85 * dependency is available. If set() has already been called, this method will assert. 86 * 87 * @param object The object to supply. 88 */ set(@ullable T object)89 public void set(@Nullable T object) { 90 mThreadChecker.assertOnValidThread(); 91 assert !mPromise.isFulfilled(); 92 mPromise.fulfill(object); 93 } 94 tryDoSet()95 private void tryDoSet() { 96 if (mDoSetCalled) return; 97 doSet(); 98 mDoSetCalled = true; 99 } 100 } 101