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