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&lt;Optional&lt;MyObject&gt;&gt; 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&lt;Optional&lt;MyObject&gt;&gt; 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}