1 /*
2  * Copyright (C) 2023 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 
17 package android.tools.traces
18 
19 /**
20  * The utility class to wait a condition with customized options. The default retry policy is 5
21  * times with interval 1 second.
22  *
23  * @param <T> The type of the object to validate.
24  *
25  * <p>Sample:</p> <pre> // Simple case. if (Condition.waitFor("true value", () -> true)) {
26  *
27  * ```
28  *     println("Success");
29  * ```
30  *
31  * } // Wait for customized result with customized validation. String result =
32  * WaitForCondition.Builder(supplier = () -> "Result string")
33  *
34  * ```
35  *         .withCondition(str -> str.equals("Expected string"))
36  *         .withRetryIntervalMs(500)
37  *         .withRetryLimit(3)
38  *         .onFailure(str -> println("Failed on " + str)))
39  *         .build()
40  *         .waitFor()
41  * ```
42  *
43  * </pre>
44  *
45  * @param condition If it returns true, that means the condition is satisfied.
46  */
47 class WaitCondition<T>
48 private constructor(
49     private val supplier: () -> T,
50     private val condition: Condition<T>,
51     private val retryLimit: Int,
52     private val onLog: ((String, Boolean) -> Unit)?,
53     private val onFailure: ((T) -> Any)?,
54     private val onRetry: ((T) -> Any)?,
55     private val onSuccess: ((T) -> Any)?,
56     private val onStart: ((String) -> Any)?,
57     private val onEnd: (() -> Any)?
58 ) {
59     /** @return `false` if the condition does not satisfy within the time limit. */
waitFornull60     fun waitFor(): Boolean {
61         onStart?.invoke("waitFor")
62         try {
63             return doWaitFor()
64         } finally {
65             onEnd?.invoke()
66         }
67     }
68 
doWaitFornull69     private fun doWaitFor(): Boolean {
70         onLog?.invoke("***Waiting for $condition", false)
71         var currState: T? = null
72         var success = false
73         for (i in 0..retryLimit) {
74             val result = doWaitForRetry(i)
75             success = result.first
76             currState = result.second
77             if (success) {
78                 break
79             } else if (i < retryLimit) {
80                 onRetry?.invoke(currState)
81             }
82         }
83 
84         return if (success) {
85             true
86         } else {
87             doNotifyFailure(currState)
88             false
89         }
90     }
91 
doWaitForRetrynull92     private fun doWaitForRetry(retryNr: Int): Pair<Boolean, T> {
93         onStart?.invoke("doWaitForRetry")
94         try {
95             val currState = supplier.invoke()
96             return if (condition.isSatisfied(currState)) {
97                 onLog?.invoke("***Waiting for $condition ... Success!", false)
98                 onSuccess?.invoke(currState)
99                 Pair(true, currState)
100             } else {
101                 val detailedMessage = condition.getMessage(currState)
102                 onLog?.invoke("***Waiting for $detailedMessage... retry=${retryNr + 1}", true)
103                 Pair(false, currState)
104             }
105         } finally {
106             onEnd?.invoke()
107         }
108     }
109 
doNotifyFailurenull110     private fun doNotifyFailure(currState: T?) {
111         val detailedMessage =
112             if (currState != null) {
113                 condition.getMessage(currState)
114             } else {
115                 condition.toString()
116             }
117         onLog?.invoke("***Waiting for $detailedMessage ... Failed!", true)
118         if (onFailure != null) {
119             require(currState != null) { "Missing last result for failure notification" }
120             onFailure.invoke(currState)
121         }
122     }
123 
124     class Builder<T>(private val supplier: () -> T, private var retryLimit: Int) {
125         private val conditions = mutableListOf<Condition<T>>()
126         private var onStart: ((String) -> Any)? = null
127         private var onEnd: (() -> Any)? = null
128         private var onFailure: ((T) -> Any)? = null
129         private var onRetry: ((T) -> Any)? = null
130         private var onSuccess: ((T) -> Any)? = null
131         private var onLog: ((String, Boolean) -> Unit)? = null
132 
<lambda>null133         fun withCondition(condition: Condition<T>) = apply { conditions.add(condition) }
134 
<lambda>null135         fun withCondition(message: String, condition: (T) -> Boolean) = apply {
136             withCondition(Condition(message, condition))
137         }
138 
spreadConditionListnull139         private fun spreadConditionList(): List<Condition<T>> =
140             conditions.flatMap {
141                 if (it is ConditionList<T>) {
142                     it.conditions
143                 } else {
144                     listOf(it)
145                 }
146             }
147 
148         /**
149          * Executes the action when the condition does not satisfy within the time limit. The passed
150          * object to the consumer will be the last result from the supplier.
151          */
<lambda>null152         fun onFailure(onFailure: (T) -> Any): Builder<T> = apply { this.onFailure = onFailure }
153 
<lambda>null154         fun onLog(onLog: (String, Boolean) -> Unit): Builder<T> = apply { this.onLog = onLog }
155 
<lambda>null156         fun onRetry(onRetry: ((T) -> Any)? = null): Builder<T> = apply { this.onRetry = onRetry }
157 
<lambda>null158         fun onStart(onStart: ((String) -> Any)? = null): Builder<T> = apply {
159             this.onStart = onStart
160         }
161 
<lambda>null162         fun onEnd(onEnd: (() -> Any)? = null): Builder<T> = apply { this.onEnd = onEnd }
163 
<lambda>null164         fun onSuccess(onRetry: ((T) -> Any)? = null): Builder<T> = apply {
165             this.onSuccess = onRetry
166         }
167 
buildnull168         fun build(): WaitCondition<T> =
169             WaitCondition(
170                 supplier,
171                 ConditionList(spreadConditionList()),
172                 retryLimit,
173                 onLog,
174                 onFailure,
175                 onRetry,
176                 onSuccess,
177                 onStart,
178                 onEnd
179             )
180     }
181 }
182