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