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;
023
024import java.time.Duration;
025import java.util.concurrent.TimeUnit;
026import java.util.function.IntToLongFunction;
027import java.util.function.Supplier;
028
029/**
030 * Helpers methods to build {@link RetryPolicy}.
031 * 
032 * @since 1.0.0
033 *
034 */
035public final class RetryPolicies {
036
037        /**
038         * Retry Policy to just do one try.
039         * 
040         */
041        public static final RetryPolicy RETRY_ONLY_ONCE = of(1, 1);
042
043        private RetryPolicies() {
044        }
045
046        /**
047         * Create a new RetryPolicy with constant wait time.
048         * <p>
049         * For example,
050         * 
051         * <pre>
052         * RetryPolicy incremental = RetryPolicies.of(3, 12);
053         * </pre>
054         * 
055         * Then following of (re-)tries will be (maximum retries):
056         * <ol>
057         * <li>Do a first try</li>
058         * <li>Wait 12 ms</li>
059         * <li>Do a second retry</li>
060         * <li>Wait 12 ms</li>
061         * <li>Do a third retry</li>
062         * </ol>
063         * 
064         * @param count
065         *            the number of retry.
066         * @param ms
067         *            the constant wait time in ms.
068         * @return the RetryPolicy
069         */
070        public static RetryPolicy of(int count, long ms) {
071                return of(count, addToString(l -> ms, () -> String.format("Constant wait time of %s ms", ms)));
072        }
073
074        /**
075         * Create a new RetryPolicy with constant wait time.
076         * <p>
077         * For example,
078         * 
079         * <pre>
080         * RetryPolicy incremental = RetryPolicies.of(3, 15, TimeUnit.MILLISECONDS);
081         * </pre>
082         * 
083         * Then following of (re-)tries will be (maximum retries):
084         * <ol>
085         * <li>Do a first try</li>
086         * <li>Wait 15 ms</li>
087         * <li>Do a second retry</li>
088         * <li>Wait 15 ms</li>
089         * <li>Do a third retry</li>
090         * </ol>
091         * 
092         * @param count
093         *            the number of retry.
094         * @param value
095         *            the wait time
096         * @param unit
097         *            the unit of the wait time.
098         * @return the RetryPolicy
099         */
100        public static RetryPolicy of(int count, long value, TimeUnit unit) {
101                return of(count, requireNonNull(unit, "unit can't be null").toMillis(value));
102        }
103
104        /**
105         * Create a new RetryPolicy with constant wait time.
106         * <p>
107         * For example,
108         * 
109         * <pre>
110         * RetryPolicy incremental = RetryPolicies.of(3, Duration.ofMillis(10));
111         * </pre>
112         * 
113         * Then following of (re-)tries will be (maximum retries):
114         * <ol>
115         * <li>Do a first try</li>
116         * <li>Wait 10 ms</li>
117         * <li>Do a second retry</li>
118         * <li>Wait 10 ms</li>
119         * <li>Do a third retry</li>
120         * </ol>
121         * 
122         * @param count
123         *            the number of retry.
124         * @param duration
125         *            the constant duration to wait.
126         * @return the RetryPolicy
127         */
128        public static RetryPolicy of(int count, Duration duration) {
129                return of(count, requireNonNull(duration, "duration can't be null").toMillis());
130        }
131
132        /**
133         * Create a new RetryPolicy, by using a generic function to compute the retry
134         * duration.
135         * <p>
136         * For example, using this definition :
137         * 
138         * <pre>
139         * RetryPolicy incremental = RetryPolicies.of(3, c -&gt; (long) (Math.random() * 100));
140         * </pre>
141         * 
142         * Then following of (re-)tries will be (maximum retries):
143         * <ol>
144         * <li>Do a first try</li>
145         * <li>Wait a random time between 0 and 100 ms</li>
146         * <li>Do a second retry</li>
147         * <li>Wait a random time between 0 and 100 ms</li>
148         * <li>Do a third retry</li>
149         * </ol>
150         * 
151         * @param count
152         *            the number of retry. A value of 1 is to do only one try, without
153         *            sleep.
154         * @param retryToWaitTime
155         *            the function to compute the wait time based on the retry. The
156         *            received value by this function starts with 1, and this will be
157         *            the first retry (meaning a the initial try has been done before).
158         * @return the RetryPolicy
159         */
160        public static RetryPolicy of(int count, IntToLongFunction retryToWaitTime) {
161                return new RetryPolicy() {
162
163                        @Override
164                        public void sleepBetweenRetry(int retry) {
165                                RetryPolicies.sleepBetweenRetry(retryToWaitTime.applyAsLong(retry));
166                        }
167
168                        @Override
169                        public int getCount() {
170                                return count;
171                        }
172
173                        @Override
174                        public String toString() {
175                                return String.format("total count = %s, with sleep method = %s", count, retryToWaitTime);
176                        }
177                };
178        }
179
180        /**
181         * Create a new RetryPolicy, that wait each time more time : first time the
182         * received duration, second time twice, etc.
183         * <p>
184         * For example, using this definition :
185         * 
186         * <pre>
187         * RetryPolicy incremental = RetryPolicies.ofIncremental(3, 20);
188         * </pre>
189         * 
190         * Then following of (re-)tries will be (maximum retries):
191         * <ol>
192         * <li>Do a first try</li>
193         * <li>Wait 20ms</li>
194         * <li>Do a second retry</li>
195         * <li>Wait 40ms</li>
196         * <li>Do a third retry</li>
197         * </ol>
198         * 
199         * @param count
200         *            the number of retry.
201         * @param ms
202         *            the time in ms that will be combined with the retry number
203         * @return the RetryPolicy
204         */
205        public static RetryPolicy ofIncremental(int count, long ms) {
206                return of(count, addToString(retry -> retry * ms, () -> String.format("Incremental retry based on %s ms", ms)));
207        }
208
209        /**
210         * Create a new RetryPolicy, that wait each time more time : first time the
211         * received duration, second time twice, etc.
212         * <p>
213         * For example, using this definition :
214         * 
215         * <pre>
216         * RetryPolicy incremental = RetryPolicies.ofIncremental(3, Duration.ofMillis(50));
217         * </pre>
218         * 
219         * Then following of (re-)tries will be (maximum retries):
220         * <ol>
221         * <li>Do a first try</li>
222         * <li>Wait 50ms</li>
223         * <li>Do a second retry</li>
224         * <li>Wait 100ms</li>
225         * <li>Do a third retry</li>
226         * </ol>
227         * 
228         * @param count
229         *            the number of retry.
230         * @param duration
231         *            the duration that will be combined with the retry number
232         * @return the RetryPolicy
233         */
234        public static RetryPolicy ofIncremental(int count, Duration duration) {
235                return ofIncremental(count, requireNonNull(duration, "duration can't be null").toMillis());
236        }
237
238        private static IntToLongFunction addToString(IntToLongFunction target, Supplier<String> toString) {
239                return new IntToLongFunction() {
240
241                        @Override
242                        public long applyAsLong(int value) {
243                                return target.applyAsLong(value);
244                        }
245
246                        @Override
247                        public String toString() {
248                                return toString.get();
249                        }
250                };
251        }
252
253        private static void sleepBetweenRetry(long ms) {
254                try {
255                        Thread.sleep(ms);
256                } catch (InterruptedException e) {
257                        // ignore
258                }
259        }
260}