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