001/** 002 * Powerunit - A JDK1.8 test framework 003 * Copyright (C) 2014 Mathieu Boretti. 004 * 005 * This file is part of Powerunit 006 * 007 * Powerunit is free software: you can redistribute it and/or modify 008 * it under the terms of the GNU General Public License as published by 009 * the Free Software Foundation, either version 3 of the License, or 010 * (at your option) any later version. 011 * 012 * Powerunit is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 015 * GNU General Public License for more details. 016 * 017 * You should have received a copy of the GNU General Public License 018 * along with Powerunit. If not, see <http://www.gnu.org/licenses/>. 019 */ 020package ch.powerunit.extensions.async.lang; 021 022import static java.util.Objects.requireNonNull; 023import static java.util.Optional.ofNullable; 024import static java.util.concurrent.Executors.callable; 025 026import java.util.Optional; 027import java.util.concurrent.Callable; 028import java.util.concurrent.CompletableFuture; 029import java.util.function.Predicate; 030import java.util.function.Supplier; 031 032import ch.powerunit.extensions.async.impl.WaitResultImpl; 033 034/** 035 * This class is the entry point for this library to support async operation 036 * inside test. 037 * <p> 038 * Use one of the provided methods to repeatedly get some data, until some 039 * condition. 040 * 041 * @since 1.0.0 - Before, this class was under 042 * {@link ch.powerunit.extensions.async}. This change is linked with java 043 * 9 module, as it will not be possible to have the implementation in a 044 * sub package of the exported one. 045 * @since 1.1.0 - Starting from version 1.1.0, the {@link System.Logger} feature 046 * is used to do some logging of the system. One goal is to provide a way 047 * to the user to see when the system is waiting for example. Also, some 048 * methods were added to decorate {@link Callable} and {@link Predicate} 049 * to add toString to describe them. 050 */ 051public final class WaitResult { 052 private WaitResult() { 053 } 054 055 /** 056 * Start the builder to create an instance of {@link CompletableFuture} based on 057 * the result of the received action, with repetition until some condition. 058 * <h1>Simple sample</h1> 059 * 060 * <pre> 061 * CompletableFuture<Optional<MyObject>> exec = WaitResult.of(MyObject::myCallable).expecting(MyObject::myControl) 062 * .repeat(10).every(10, TimeUnit.MILLISECONDS).asyncExec(); 063 * </pre> 064 * 065 * @param action 066 * the {@link Callable} providing the result. 067 * @param <T> 068 * The type of the result. 069 * @return {@link WaitResultBuilder1 the next step of the builder} 070 * @throws NullPointerException 071 * if action is null 072 */ 073 public static <T> WaitResultBuilder1<T> of(Callable<T> action) { 074 return of(action, null); 075 } 076 077 /** 078 * Start the builder to create an instance of {@link CompletableFuture} based on 079 * the result of the received action, with repetition until some condition. 080 * 081 * @param action 082 * the {@link Callable} providing the result. 083 * @param actionOnFinish 084 * register an action after the result is retrieved (success or 085 * failure). This may be used to register some resource cleanup. 086 * @param <T> 087 * The type of the result. 088 * @return {@link WaitResultBuilder1 the next step of the builder} 089 * @throws NullPointerException 090 * if action is null 091 * @since 1.1.0 092 */ 093 public static <T> WaitResultBuilder1<T> of(Callable<T> action, Runnable actionOnFinish) { 094 requireNonNull(action, "action can't be null"); 095 return new WaitResultBuilder1<T>() { 096 097 @Override 098 public WaitResultBuilder3<T> expecting(Predicate<T> acceptingClause) { 099 return retry -> ((WaitResultBuilder5<T>) new WaitResultImpl<>( 100 asFilteredCallable(action, acceptingClause), retry)::get).onFinish(actionOnFinish); 101 } 102 103 @Override 104 public WaitResultBuilder2<T> ignoreException(boolean alsoDontFailWhenNoResultAndException) { 105 return predicate -> retry -> ((WaitResultBuilder5<T>) new WaitResultImpl<>( 106 asFilteredCallable(action, predicate), alsoDontFailWhenNoResultAndException, retry)::get) 107 .onFinish(actionOnFinish); 108 } 109 }; 110 } 111 112 private static <T> Callable<Optional<T>> asFilteredCallable(Callable<T> action, Predicate<T> acceptingClause) { 113 requireNonNull(action, "action can't be null"); 114 requireNonNull(acceptingClause, "acceptingClause can't be null"); 115 return callableWithToString(() -> ofNullable(action.call()).filter(acceptingClause), 116 () -> String.format("Action = %s, AcceptingClause = %s", action, acceptingClause)); 117 } 118 119 /** 120 * Start the builder to create an instance of {@link CompletableFuture} based on 121 * execution of the received action, with repetition until some condition. 122 * <p> 123 * 124 * @param supplier 125 * the {@link Supplier} to be executed. 126 * @param <T> 127 * The type of the result. 128 * @return {@link WaitResultBuilder1 the next step of the builder} 129 * @since 1.1.0 130 * @throws NullPointerException 131 * if supplier is null 132 */ 133 public static <T> WaitResultBuilder1<T> ofSupplier(Supplier<T> supplier) { 134 requireNonNull(supplier, "supplier can't be null"); 135 return of(callableWithToString(supplier::get, () -> supplier.toString())); 136 } 137 138 /** 139 * Start the builder to create an instance of {@link CompletableFuture} based on 140 * execution of the received action, with repetition until some condition. 141 * <p> 142 * In this case, it is assumed that the received action throws unchecked 143 * exception when the condition is not yet OK. The returned Optional will be 144 * present in case of success. 145 * 146 * @param action 147 * the {@link Runnable} to be executed. 148 * @return {@link WaitResultBuilder3 the next step of the builder} 149 * @since 1.0.0 150 * @throws NullPointerException 151 * if action is null 152 */ 153 public static WaitResultBuilder3<Boolean> ofRunnable(Runnable action) { 154 requireNonNull(action, "action can't be null"); 155 return of(callableWithToString(callable(action, true), () -> action.toString())).ignoreException(true) 156 .expecting(predicateWithToString(b -> b, () -> "Expecting true")); 157 } 158 159 /** 160 * Start the builder to create an instance of {@link CompletableFuture} based on 161 * repeated control on the mutable object. 162 * <h1>Simple sample</h1> 163 * 164 * <pre> 165 * CompletableFuture<Optional<MyObject>> exec = WaitResult.on(myObject).expecting(MyObject::myControl).repeat(100) 166 * .every(10, TimeUnit.MILLISECONDS).asyncExec(); 167 * </pre> 168 * 169 * @param mutableObject 170 * the mutable object to be checked. 171 * @param <T> 172 * The type of the result. 173 * @return {@link WaitResultBuilder2 the next step of the builder} 174 */ 175 public static <T> WaitResultBuilder2<T> on(T mutableObject) { 176 return of(callableWithToString(() -> mutableObject, () -> String.format("on object %s", mutableObject))); 177 } 178 179 /** 180 * Start the builder to create an instance of {@link CompletableFuture} based on 181 * repeated control on a method returning a boolean. 182 * 183 * @param conditionSupplier 184 * the boolean supplier 185 * @return {@link WaitResultBuilder3 the next step of the builder} 186 * @throws NullPointerException 187 * if conditionSupplier is null 188 */ 189 public static WaitResultBuilder3<Boolean> onCondition(Supplier<Boolean> conditionSupplier) { 190 requireNonNull(conditionSupplier, "conditionSupplier can't be null"); 191 return of(callableWithToString(conditionSupplier::get, () -> conditionSupplier.toString())) 192 .expecting(predicateWithToString(b -> b, () -> "Expecting true")); 193 } 194 195 /** 196 * Start the builder to create an instance of {@link CompletableFuture} based on 197 * repeated control of a call that is assumed as OK when an exception is thrown. 198 * 199 * @param action 200 * the action that is expected to thrown an exception. 201 * @return {@link WaitResultBuilder3 the next step of the builder} 202 * @since 1.0.0 203 * @throws NullPointerException 204 * if action is null 205 */ 206 public static WaitResultBuilder3<Exception> forException(Callable<?> action) { 207 requireNonNull(action, "action can't be null"); 208 return of(asCallableForException(action, e -> e)).dontIgnoreException().expectingNotNull(); 209 } 210 211 /** 212 * Start the builder to create an instance of {@link CompletableFuture} based on 213 * repeated control of a call that is assumed as done when a a specific 214 * exception is thrown. 215 * 216 * @param action 217 * the action that is expected to thrown an exception. 218 * @param targetException 219 * the expected Exception class 220 * @param <T> 221 * the expected exception type 222 * @return {@link WaitResultBuilder1 the next step of the builder} 223 * @since 1.1.0 224 * @throws NullPointerException 225 * if action or targetException is null 226 */ 227 @SuppressWarnings("unchecked") 228 public static <T extends Exception> WaitResultBuilder1<T> forException(Callable<?> action, 229 Class<T> targetException) { 230 requireNonNull(action, "action can't be null"); 231 requireNonNull(targetException, "targetException can't be null"); 232 return of(asCallableForException(action, e -> (T) Optional.of(e) 233 .filter(c -> targetException.isAssignableFrom(c.getClass())).orElseThrow(() -> e))); 234 } 235 236 private static interface ExceptionMapper<T extends Exception> { 237 T handleException(Exception e) throws Exception; 238 } 239 240 private static <T extends Exception> Callable<T> asCallableForException(Callable<?> action, 241 ExceptionMapper<T> exceptionHandler) { 242 return callableWithToString(() -> { 243 try { 244 action.call(); 245 return null; 246 } catch (Exception e) { 247 return exceptionHandler.handleException(e); 248 } 249 }, () -> action.toString()); 250 } 251 252 // Helper method for logging 253 /** 254 * Modify a Callable to add a toString. 255 * <p> 256 * <i>The goal of this method is to provide a way to have lambda, used for 257 * example in the context of this library, that are decorated with a 258 * {@code toString} method.</i> Later, when this Callable is used in log, it is 259 * possible to have a meaningful description and not the default 260 * {@code toString}. 261 * 262 * @param callable 263 * the Callable to be decorated. 264 * @param toString 265 * the Supplier to be used as toString method. 266 * @param <T> 267 * the return type of the Callable 268 * @return the decorated Callable. 269 * @throws NullPointerException 270 * if callable or toString is null. 271 * @since 1.1.0 272 */ 273 public static <T> Callable<T> callableWithToString(Callable<T> callable, Supplier<String> toString) { 274 requireNonNull(callable, "callable can't be null"); 275 requireNonNull(toString, "toString can't be null"); 276 return new Callable<T>() { 277 278 @Override 279 public T call() throws Exception { 280 return callable.call(); 281 } 282 283 @Override 284 public String toString() { 285 return toString.get(); 286 } 287 }; 288 } 289 290 /** 291 * Modify a Predicate to add a toString. 292 * <p> 293 * <i>The goal of this method is to provide a way to have lambda, used for 294 * example in the context of this library, that are decorated with a 295 * {@code toString} method.</i> Later, when this Predicate is used in log, it is 296 * possible to have a meaningful description and not the default 297 * {@code toString}. 298 * 299 * @param predicate 300 * the Predicate to be decorated. 301 * @param toString 302 * the Supplier to be used as toString method. 303 * @param <T> 304 * the input type of the Predicate. 305 * @return the decorated Predicate. 306 * @throws NullPointerException 307 * if predicate or toString is null. 308 * @since 1.1.0 309 */ 310 public static <T> Predicate<T> predicateWithToString(Predicate<T> predicate, Supplier<String> toString) { 311 requireNonNull(predicate, "predicate can't be null"); 312 requireNonNull(toString, "toString can't be null"); 313 return new Predicate<T>() { 314 315 @Override 316 public boolean test(T t) { 317 return predicate.test(t); 318 } 319 320 @Override 321 public String toString() { 322 return toString.get(); 323 } 324 }; 325 } 326 327}