• 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.traces
18 
19 import java.util.function.BiConsumer
20 import java.util.function.Consumer
21 import java.util.function.Predicate
22 import java.util.function.Supplier
23 
24 /**
25  * The utility class to wait a condition with customized options. The default retry policy is 5
26  * times with interval 1 second.
27  *
28  * @param <T> The type of the object to validate.
29  *
30  * <p>Sample:</p> <pre> // Simple case. if (Condition.waitFor("true value", () -> true)) {
31  *
32  * ```
33  *     println("Success");
34  * ```
35  *
36  * } // Wait for customized result with customized validation. String result =
37  * WaitForCondition.Builder(supplier = () -> "Result string")
38  *
39  * ```
40  *         .withCondition(str -> str.equals("Expected string"))
41  *         .withRetryIntervalMs(500)
42  *         .withRetryLimit(3)
43  *         .onFailure(str -> println("Failed on " + str)))
44  *         .build()
45  *         .waitFor()
46  * ```
47  *
48  * </pre>
49  *
50  * @param condition If it returns true, that means the condition is satisfied.
51  */
52 class WaitCondition<T>
53 private constructor(
54     private val supplier: Supplier<T>,
55     private val condition: Condition<T>,
56     private val retryLimit: Int,
57     private val onLog: BiConsumer<String, Boolean>?,
58     private val onFailure: Consumer<T>?,
59     private val onRetry: Consumer<T>?,
60     private val onSuccess: Consumer<T>?,
61     private val onStart: Consumer<String>?,
62     private val onEnd: Consumer<String>?,
63 ) {
64     /** @return `false` if the condition does not satisfy within the time limit. */
waitFornull65     fun waitFor(): Boolean {
66         onStart?.accept("waitFor")
67         try {
68             return doWaitFor()
69         } finally {
70             onEnd?.accept("done waitFor")
71         }
72     }
73 
doWaitFornull74     private fun doWaitFor(): Boolean {
75         onLog?.accept("***Waiting for $condition", false)
76         var currState: T? = null
77         var success = false
78         for (i in 0..retryLimit) {
79             val result = doWaitForRetry(i)
80             success = result.first
81             currState = result.second
82             if (success) {
83                 break
84             } else if (i < retryLimit) {
85                 onRetry?.accept(currState)
86             }
87         }
88 
89         return if (success) {
90             true
91         } else {
92             doNotifyFailure(currState)
93             false
94         }
95     }
96 
doWaitForRetrynull97     private fun doWaitForRetry(retryNr: Int): Pair<Boolean, T> {
98         onStart?.accept("doWaitForRetry")
99         try {
100             val currState = supplier.get()
101             return if (condition.isSatisfied(currState)) {
102                 onLog?.accept("***Waiting for $condition ... Success!", false)
103                 onSuccess?.accept(currState)
104                 Pair(true, currState)
105             } else {
106                 val detailedMessage = condition.getMessage(currState)
107                 val isError = retryNr + 1 >= retryLimit
108                 onLog?.accept(
109                     "***Waiting for $detailedMessage... retry=${retryNr + 1}/$retryLimit", isError)
110                 Pair(false, currState)
111             }
112         } finally {
113             onEnd?.accept("done doWaitForRetry")
114         }
115     }
116 
doNotifyFailurenull117     private fun doNotifyFailure(currState: T?) {
118         val detailedMessage =
119             if (currState != null) {
120                 condition.getMessage(currState)
121             } else {
122                 condition.toString()
123             }
124         onLog?.accept("***Waiting for $detailedMessage ... Failed!", true)
125         if (onFailure != null) {
126             require(currState != null) { "Missing last result for failure notification" }
127             onFailure.accept(currState)
128         }
129     }
130 
131     class Builder<T>(private var retryLimit: Int, private val supplier: Supplier<T>) {
132         private val conditions = mutableListOf<Condition<T>>()
133         private var onStart: Consumer<String>? = null
134         private var onEnd: Consumer<String>? = null
135         private var onFailure: Consumer<T>? = null
136         private var onRetry: Consumer<T>? = null
137         private var onSuccess: Consumer<T>? = null
138         private var onLog: BiConsumer<String, Boolean>? = null
139 
<lambda>null140         fun withCondition(condition: Condition<T>) = apply { conditions.add(condition) }
141 
<lambda>null142         fun withCondition(message: String, condition: Predicate<T>) = apply {
143             withCondition(Condition(message, condition))
144         }
145 
spreadConditionListnull146         private fun spreadConditionList(): List<Condition<T>> =
147             conditions.flatMap {
148                 if (it is ConditionList<T>) {
149                     it.conditions
150                 } else {
151                     listOf(it)
152                 }
153             }
154 
155         /**
156          * Executes the action when the condition does not satisfy within the time limit. The passed
157          * object to the consumer will be the last result from the supplier.
158          */
<lambda>null159         fun onFailure(onFailure: Consumer<T>): Builder<T> = apply { this.onFailure = onFailure }
160 
<lambda>null161         fun onLog(onLog: BiConsumer<String, Boolean>): Builder<T> = apply { this.onLog = onLog }
162 
<lambda>null163         fun onRetry(onRetry: Consumer<T>? = null): Builder<T> = apply { this.onRetry = onRetry }
164 
<lambda>null165         fun onStart(onStart: Consumer<String>? = null): Builder<T> = apply {
166             this.onStart = onStart
167         }
168 
<lambda>null169         fun onEnd(onEnd: Consumer<String>? = null): Builder<T> = apply { this.onEnd = onEnd }
170 
<lambda>null171         fun onSuccess(onRetry: Consumer<T>? = null): Builder<T> = apply { this.onSuccess = onRetry }
172 
buildnull173         fun build(): WaitCondition<T> =
174             WaitCondition(
175                 supplier,
176                 ConditionList(spreadConditionList()),
177                 retryLimit,
178                 onLog,
179                 onFailure,
180                 onRetry,
181                 onSuccess,
182                 onStart,
183                 onEnd,
184             )
185     }
186 }
187