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.NonNull; 8 import androidx.annotation.Nullable; 9 10 import org.chromium.base.Callback; 11 12 import java.util.function.Function; 13 14 /** 15 * Useful when two observable suppliers are chained together. The client class may only want to know 16 * the value of the second, or "target", supplier. But to track this the client needs to observe the 17 * first, or "parent", supplier, and then [un]observe the current target. Instead this class is a 18 * single observable supplier that holds the current target value, greatly simplifying the client's 19 * job. 20 * 21 * <p>Attempts to only maintain observers on the relevant observers when there's an observer on this 22 * class. Clients should still remove themselves as observers from this class when done. 23 * 24 * @param <P> The parent object that's holding a reference to the target. 25 * @param <T> The target type that the client wants to observe. 26 */ 27 public class TransitiveObservableSupplier<P, T> implements ObservableSupplier<T> { 28 // Used to hold observers and current state. However the current value is only valid when there 29 // are observers, otherwise is may be stale. 30 private final @NonNull ObservableSupplierImpl<T> mDelegateSupplier = 31 new ObservableSupplierImpl<>(); 32 33 private final @NonNull Callback<P> mOnParentSupplierChangeCallback = 34 this::onParentSupplierChange; 35 private final @NonNull Callback<T> mOnTargetSupplierChangeCallback = 36 this::onTargetSupplierChange; 37 private final @NonNull ObservableSupplier<P> mParentSupplier; 38 private final @NonNull Function<P, ObservableSupplier<T>> mUnwrapFunction; 39 40 // When this is set, then mOnTargetSupplierChangeCallback is an observer of the object 41 // referenced by mCurrentTargetSupplier. When this value is changed, the observer must be 42 // removed. 43 private @Nullable ObservableSupplier<T> mCurrentTargetSupplier; 44 TransitiveObservableSupplier( ObservableSupplier<P> parentSupplier, Function<P, ObservableSupplier<T>> unwrapFunction)45 public TransitiveObservableSupplier( 46 ObservableSupplier<P> parentSupplier, 47 Function<P, ObservableSupplier<T>> unwrapFunction) { 48 mParentSupplier = parentSupplier; 49 mUnwrapFunction = unwrapFunction; 50 } 51 52 @Override addObserver(Callback<T> obs)53 public T addObserver(Callback<T> obs) { 54 if (!mDelegateSupplier.hasObservers()) { 55 onParentSupplierChange(mParentSupplier.addObserver(mOnParentSupplierChangeCallback)); 56 } 57 return mDelegateSupplier.addObserver(obs); 58 } 59 60 @Override removeObserver(Callback<T> obs)61 public void removeObserver(Callback<T> obs) { 62 mDelegateSupplier.removeObserver(obs); 63 if (!mDelegateSupplier.hasObservers()) { 64 mParentSupplier.removeObserver(mOnParentSupplierChangeCallback); 65 if (mCurrentTargetSupplier != null) { 66 mCurrentTargetSupplier.removeObserver(mOnTargetSupplierChangeCallback); 67 mCurrentTargetSupplier = null; 68 } 69 } 70 } 71 72 @Override get()73 public @Nullable T get() { 74 if (mDelegateSupplier.hasObservers()) { 75 return mDelegateSupplier.get(); 76 } 77 78 // If we have no observers, the value stored by mDelegateSupplier might not be current. 79 P parentValue = mParentSupplier.get(); 80 if (parentValue != null) { 81 ObservableSupplier<T> targetSupplier = mUnwrapFunction.apply(parentValue); 82 if (targetSupplier != null) { 83 return targetSupplier.get(); 84 } 85 } 86 return null; 87 } 88 89 /** 90 * Conceptually this just removes our observer from the old target supplier, and our observer to 91 * to the new target supplier. In practice this is full of null checks. We also have to make 92 * sure we keep our delegate supplier's value up to date, which is also what drives client 93 * observations. 94 */ onParentSupplierChange(@ullable P parentValue)95 private void onParentSupplierChange(@Nullable P parentValue) { 96 if (mCurrentTargetSupplier != null) { 97 mCurrentTargetSupplier.removeObserver(mOnTargetSupplierChangeCallback); 98 } 99 100 // Keep track of the current target supplier, because if this ever changes, we'll need to 101 // remove our observer from it. 102 mCurrentTargetSupplier = parentValue == null ? null : mUnwrapFunction.apply(parentValue); 103 104 if (mCurrentTargetSupplier == null) { 105 onTargetSupplierChange(null); 106 } else { 107 // While addObserver will call us if a value is already set, we do not want to depend on 108 // that for two reasons. If there is no value set, we need to null out our supplier now. 109 // And if there is a value set, we're going to get invoked asynchronously, which means 110 // our delegate supplier could be in an intermediately incorrect state. By just setting 111 // our delegate eagerly we avoid both problems. 112 onTargetSupplierChange( 113 mCurrentTargetSupplier.addObserver(mOnTargetSupplierChangeCallback)); 114 } 115 } 116 onTargetSupplierChange(@ullable T targetValue)117 private void onTargetSupplierChange(@Nullable T targetValue) { 118 mDelegateSupplier.set(targetValue); 119 } 120 } 121