• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.common.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", /* isError */ 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!", /* isError */ false)
98                 onSuccess?.invoke(currState)
99                 Pair(true, currState)
100             } else {
101                 val detailedMessage = condition.getMessage(currState)
102                 onLog?.invoke(
103                     "***Waiting for $detailedMessage... retry=${retryNr + 1}",
104                     /* isError */ true
105                 )
106                 Pair(false, currState)
107             }
108         } finally {
109             onEnd?.invoke()
110         }
111     }
112 
doNotifyFailurenull113     private fun doNotifyFailure(currState: T?) {
114         val detailedMessage =
115             if (currState != null) {
116                 condition.getMessage(currState)
117             } else {
118                 condition.toString()
119             }
120         onLog?.invoke("***Waiting for $detailedMessage ... Failed!", /* isError */ true)
121         if (onFailure != null) {
122             require(currState != null) { "Missing last result for failure notification" }
123             onFailure.invoke(currState)
124         }
125     }
126 
127     class Builder<T>(private val supplier: () -> T, private var retryLimit: Int) {
128         private val conditions = mutableListOf<Condition<T>>()
129         private var onStart: ((String) -> Any)? = null
130         private var onEnd: (() -> Any)? = null
131         private var onFailure: ((T) -> Any)? = null
132         private var onRetry: ((T) -> Any)? = null
133         private var onSuccess: ((T) -> Any)? = null
134         private var onLog: ((String, Boolean) -> Unit)? = null
135 
<lambda>null136         fun withCondition(condition: Condition<T>) = apply { conditions.add(condition) }
137 
<lambda>null138         fun withCondition(message: String, condition: (T) -> Boolean) = apply {
139             withCondition(Condition(message, condition))
140         }
141 
spreadConditionListnull142         private fun spreadConditionList(): List<Condition<T>> =
143             conditions.flatMap {
144                 if (it is ConditionList<T>) {
145                     it.conditions
146                 } else {
147                     listOf(it)
148                 }
149             }
150 
151         /**
152          * Executes the action when the condition does not satisfy within the time limit. The passed
153          * object to the consumer will be the last result from the supplier.
154          */
<lambda>null155         fun onFailure(onFailure: (T) -> Any): Builder<T> = apply { this.onFailure = onFailure }
156 
<lambda>null157         fun onLog(onLog: (String, Boolean) -> Unit): Builder<T> = apply { this.onLog = onLog }
158 
<lambda>null159         fun onRetry(onRetry: ((T) -> Any)? = null): Builder<T> = apply { this.onRetry = onRetry }
160 
<lambda>null161         fun onStart(onStart: ((String) -> Any)? = null): Builder<T> = apply {
162             this.onStart = onStart
163         }
164 
<lambda>null165         fun onEnd(onEnd: (() -> Any)? = null): Builder<T> = apply { this.onEnd = onEnd }
166 
<lambda>null167         fun onSuccess(onRetry: ((T) -> Any)? = null): Builder<T> = apply {
168             this.onSuccess = onRetry
169         }
170 
buildnull171         fun build(): WaitCondition<T> =
172             WaitCondition(
173                 supplier,
174                 ConditionList(spreadConditionList()),
175                 retryLimit,
176                 onLog,
177                 onFailure,
178                 onRetry,
179                 onSuccess,
180                 onStart,
181                 onEnd
182             )
183     }
184 }
185