• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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