package de.galan.commons.util;

import static org.apache.commons.lang3.StringUtils.*;

import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;

import org.apache.commons.lang3.ObjectUtils;

import de.galan.commons.logging.Say;
import de.galan.commons.time.Durations;
import de.galan.commons.time.Sleeper;


/**
 * Invokes a Callable/ExceptionalRunnable until it runs without Exception the specified times of retries.
 */
public class Retryable {

	public static final String DEFAULT_WAIT_TIME = "1s";
	public static final long INFINITE = -1;

	private long numberOfRetries; // total number of tries
	private long numberOfTriesLeft; // number left
	private String timeToWait; // wait interval
	private String message; // message to identify retry


	Retryable(long numberOfRetries) {
		retries(numberOfRetries);
	}


	public static Retryable retry(long numberOfRetries) {
		return new Retryable(numberOfRetries);
	}


	public static Retryable infinite() {
		return new Retryable(INFINITE);
	}


	public Retryable timeToWait(String timeToWaitBetween) {
		timeToWait = ObjectUtils.defaultIfNull(timeToWaitBetween, DEFAULT_WAIT_TIME);
		return this;
	}


	public Retryable timeToWait(long millis) {
		timeToWait = Durations.humanize(millis);
		return this;
	}


	public Retryable retries(long retries) {
		numberOfRetries = retries;
		numberOfTriesLeft = numberOfRetries + 1;
		return this;
	}


	public Retryable message(String msg) {
		message = msg;
		return this;
	}


	public <V> V call(Callable<V> callable) throws Exception {
		while(true) {
			try {
				return callable.call();
			}
			catch (InterruptedException e) {
				throw e;
			}
			catch (CancellationException e) {
				throw e;
			}
			catch (Exception e) {
				numberOfTriesLeft--;
				if (numberOfRetries != INFINITE) {
					if (numberOfTriesLeft == 0) {
						throw new RetryException(numberOfRetries + " attempts to retry, failed at " + timeToWait + " interval for " + message, e,
								numberOfRetries, timeToWait, message);
					}
					if (isBlank(message)) {
						Say.info("Retrying {numberOfRetriesLeft}/{numberOfRetries} in {timeToWait}",
							numberOfRetries - numberOfTriesLeft + 1, numberOfRetries, timeToWait);
					}
					else {
						Say.info("Retrying {numberOfRetriesLeft}/{numberOfRetries} in {timeToWait} for {message}",
							numberOfRetries - numberOfTriesLeft + 1, numberOfRetries, timeToWait, message);
					}
				}
				else {
					if (isBlank(message)) {
						Say.info("Retrying {} in {}", Math.abs(numberOfTriesLeft + 1), timeToWait);
					}
					else {
						Say.info("Retrying {} in {} for {}", Math.abs(numberOfTriesLeft + 1), timeToWait, message);
					}
				}
				Sleeper.sleep(timeToWait);
			}
		}
	}


	public void run(ExceptionalRunnable runnable) throws Exception {
		call(() -> {
			runnable.run();
			return null;
		});
	}

}

@@ -25,16 +25,61 @@
 .util;%0A%0A
+import static de.galan.commons.util.Sugar.*;%0A
 import s
@@ -219,54 +219,8 @@
 n;%0A%0A
-import org.apache.commons.lang3.ObjectUtils;%0A%0A
 impo
@@ -1095,71 +1095,25 @@
 t = 
-ObjectUtils.defaultIfNull(timeToWaitBetween, DEFAULT_WAIT_TIME)
+timeToWaitBetween
 ;%0A%09%09
@@ -2657,16 +2657,25 @@
 r.sleep(
+optional(
 timeToWa
@@ -2673,24 +2673,51 @@
 (timeToWait)
+.orElse(DEFAULT_WAIT_TIME))
 ;%0A%09%09%09%7D%0A%09%09%7D%0A%09

Fix Retryable null condition