1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.adservices.shared.testing.concurrency;
17 
18 import com.android.adservices.shared.testing.Nullable;
19 
20 import com.google.common.annotations.VisibleForTesting;
21 import com.google.errorprone.annotations.concurrent.GuardedBy;
22 
23 import java.util.ArrayList;
24 import java.util.Collections;
25 import java.util.List;
26 
27 /**
28  * {@code SyncCallback} use to return an object (result).
29  *
30  * @param <R> type of the result.
31  */
32 public class ResultSyncCallback<R> extends DeviceSideSyncCallback
33         implements IResultSyncCallback<R> {
34 
35     private final Object mLock = new Object();
36 
37     @GuardedBy("mLock")
38     private R mResult;
39 
40     @GuardedBy("mLock")
41     private final List<R> mResults = new ArrayList<>();
42 
ResultSyncCallback()43     public ResultSyncCallback() {
44         super(SyncCallbackFactory.newSettingsBuilder().build());
45     }
46 
ResultSyncCallback(SyncCallbackSettings settings)47     public ResultSyncCallback(SyncCallbackSettings settings) {
48         super(settings);
49     }
50 
51     @VisibleForTesting
ResultSyncCallback(AbstractSyncCallback realCallback, SyncCallbackSettings settings)52     ResultSyncCallback(AbstractSyncCallback realCallback, SyncCallbackSettings settings) {
53         super(realCallback, settings);
54     }
55 
56     @Override
injectResult(@ullable R result)57     public final void injectResult(@Nullable R result) {
58         internalInjectResult("injectResult", result);
59     }
60 
61     // TODO(b/342448771): Make this method package protected
internalInjectResult(String name, @Nullable R result)62     protected final void internalInjectResult(String name, @Nullable R result) {
63         StringBuilder methodName = new StringBuilder(name).append("(").append(result);
64         synchronized (mLock) {
65             boolean firstCall = mResults.isEmpty();
66             if (firstCall) {
67                 mResult = result;
68             } else {
69                 // Don't set mResult
70                 methodName
71                         .append("; already called: mResult=")
72                         .append(mResult)
73                         .append(", mResults=")
74                         .append(mResults);
75             }
76             mResults.add(result);
77         }
78         super.internalSetCalled(methodName.append(')').toString());
79     }
80 
81     @Override
assertResultReceived()82     public @Nullable R assertResultReceived() throws InterruptedException {
83         super.assertCalled();
84         return getResult();
85     }
86 
87     @Override
getResult()88     public final @Nullable R getResult() {
89         synchronized (mLock) {
90             return mResult;
91         }
92     }
93 
94     @Override
getResults()95     public List<R> getResults() {
96         synchronized (mLock) {
97             return mResults.isEmpty()
98                     ? Collections.emptyList()
99                     : Collections.unmodifiableList(new ArrayList<>(mResults));
100         }
101     }
102 
103     @Override
customizeToString(StringBuilder string)104     protected void customizeToString(StringBuilder string) {
105         super.customizeToString(string);
106 
107         synchronized (mLock) {
108             List<R> results = getResults();
109             if (results.isEmpty()) {
110                 string.append(", (no result yet)");
111             } else {
112                 string.append(", result=").append(getResult()).append(", results=").append(results);
113             }
114         }
115     }
116 
117     // Ideally should be moved to some helper class (which would require unit-testint it as well)
getImmutableList(List<I> list)118     static <I> List<I> getImmutableList(List<I> list) {
119         return list.isEmpty()
120                 ? Collections.emptyList()
121                 : Collections.unmodifiableList(new ArrayList<>(list));
122     }
123 }
124