This repo contains Java functional interfaces enhanced with more behaviours
- Java 8+
- Gradle 8+ (gradle wrapper included)
Using Gradle
dependencies {
implementation 'io.github.danieleperuzzi:enhanced-functions:1.3.1'
}
Using Maven
<dependency>
<groupId>io.github.danieleperuzzi</groupId>
<artifactId>enhanced-functions</artifactId>
<version>1.3.1</version>
</dependency>
To build enhanced-functions library just run gradle build task:
on Linux
./gradlew build
on Windows
./gradlew.bat build
you can also build the library using your machine gradle installation but please be sure gradle version is at least 8.
To launch the builtin test suite for enhanced-functions library just run gradle test task:
on Linux
./gradlew test
on Windows
./gradlew.bat test
you can also test the library using your machine gradle installation but please be sure gradle version is at least 8.
RetrySupplier is a functional interface that has the same signature of the standard java Supplier
interface but has the ability to thrown exceptions - more formerly Throwables. In addiction this interface provides methods to retry itself
a defined number of times or for a certain amount of time before it throws an exception. Every time an attempt is performed
with failure outcome a message is printed through a logging interface. For further information see Logging.
This interface is useful to reiterate operations that may throw an exception until they will succeed.
retry
int numRetry = 5;
try {
// api.getResponse() returns an exception if the call hasn't completed
ApiResponse result = RetrySupplier.builder(() -> api.getResponse())
.retry(numRetry)
.get();
} catch (Throwable e) {
e.printStackTrace();
}
poll
long time = 5;
try {
// api.getResponse() returns an exception if the call hasn't completed
ApiResponse result = RetrySupplier.builder(() -> api.getResponse())
.poll(time, ChronoUnit.SECONDS)
.get();
} catch (Throwable e) {
e.printStackTrace();
}
if the time unit is omitted then the default one will be MILLIS
In case we need to customize the exception thrown on failure just pass a Supplier<? extends Throwable> exceptionSupplier
to retry
or poll
methods
retry
int numRetry = 5;
try {
// api.getResponse() returns an exception if the call hasn't completed
ApiResponse result = RetrySupplier.builder(() -> api.getResponse())
.retry(numRetry, () -> new Exception("Custom Exception"))
.get();
} catch (Throwable e) {
e.printStackTrace();
}
poll
long time = 5;
try {
// api.getResponse() returns an exception if the call hasn't completed
ApiResponse result = RetrySupplier.builder(() -> api.getResponse())
.poll(time, ChronoUnit.SECONDS, () -> new Exception("Custom Exception"))
.get();
} catch (Throwable e) {
e.printStackTrace();
}
In case the computation to be performed is an heavy computation it is possible to execute it in a different thread. By default the thread is retrieved using ForkJoinPool commonPool but a custom Executor
can be given in input.
Also a custom error can be thrown to override the original error thrown by the computation.
The returned RetrySupplier
, by retryAsync
or pollAsync
methods, basically waits for the computation
to complete in synchronous mode on the caller thread while the task is performed on a different one.
If the need is to get the result in asynchronous mode then AsyncTask may be the right choice.
For specific way to invoke async methods please refer to the RetrySupplier class directly.
retryAsync
int numRetry = 5;
try {
// api.getResponse() returns an exception if the call hasn't completed
ApiResponse result = RetrySupplier.builder(() -> api.getResponse())
.retryAsync(numRetry)
.get();
} catch (Throwable e) {
e.printStackTrace();
}
pollAsync
Since poll async mechanism is based on time, it may happen that the computation lasts longer than the time the RetrySupplier is instructed to wait. For this reason, if no custom exception is provided, then TimeoutException is thrown
long time = 5;
try {
// api.getResponse() returns an exception if the call hasn't completed
ApiResponse result = RetrySupplier.builder(() -> api.getResponse())
.pollAsync(time, ChronoUnit.SECONDS)
.get();
} catch (Throwable e) {
e.printStackTrace();
}
It may happen that the code we want to wrap into a function to reiterate until success doesn't throw any exception. In this case the code necessarily returns some value so we can create logic upon that
return null value
Retry until the result of the computation is not null
this example uses
retry(numRetry)
but it is also suitablepoll(time, ChronoUnit.SECONDS)
int numRetry = 5;
try {
// stringProvider may return null but no exception
String result = RetrySupplier.retryUntilNotNull(() -> stringProvider.get())
.retry(numRetry)
.get();
} catch (Throwable e) {
e.printStackTrace();
}
return boolean value
Retry until the result of the computation is true
this example uses
retry(numRetry)
but it is also suitablepoll(time, ChronoUnit.SECONDS)
int numRetry = 5;
try {
// booleanProvider may return false or true but no exception
boolean result = RetrySupplier.retryUntilTrue(() -> booleanProvider.get())
.retry(numRetry)
.get();
} catch (Throwable e) {
e.printStackTrace();
}
return any value
this examples use
retry(numRetry)
but it is also suitablepoll(time, ChronoUnit.SECONDS)
Retry until the result of the computation is the not-null expected result
int numRetry = 5;
try {
// stringProvider returns random strings but no exception
String result = RetrySupplier.retryUntilEqual(() -> stringProvider.get(), "Cat")
.retry(numRetry)
.get();
} catch (Throwable e) {
e.printStackTrace();
}
Retry until the result of the computation satisfies the not-null provided test
int numRetry = 5;
try {
// intProvider returns random int numbers but no exception
Integer result = RetrySupplier.retryUntilTestOk(() -> intProvider.get(), result -> result > 10)
.retry(numRetry)
.get();
} catch (Throwable e) {
e.printStackTrace();
}
RetrySupplier interface always returns the computation result in a synchronous mode but we may want to get
the result posted in a callback in order to work with event-based code.
AsyncTask
does this job in a very simple way. By default the async computation, represented by a RetrySupplier
,
is performed in a separated thread. That thread is retrieved using ForkJoinPool commonPool
but a custom Executor can be given in input.
Because AsyncTask
already executes RetrySupplier
in a different thread it is preferable to use RetrySupplier
synchronous methods, as retry
or poll
are, over their respective async methods when building complex RetrySupplier
.
Once the computation has completed, either in successful or failure case, the outcome is posted to the provided callback where it is possible to inspect the successful result or the exception.
// api.getResponse() returns an exception if the call hasn't completed
RetrySupplier<ApiResponse> retrySupplier = RetrySupplier.builder(() -> api.getResponse())
.retry(3);
AsyncTask.toAsync(retrySupplier, (result, throwable) -> {
if (result != null) {
// computation is successful
} else {
throwable.printStackTrace();
}
});
ConditionalConsumer is a functional interface that extends the standard java Consumer interface and adds the ability to process the consumer only if specific condition is met otherwise it does nothing
failure
AtomicInteger counter = new AtomicInteger(0);
Consumer<AtomicInteger> increment = c -> c.incrementAndGet();
ConditionalConsumer.builder(increment)
.acceptIf(c -> c.get() == 1) // check condition
.accept(counter);
int result = counter.get(); // result is 0 because it hasn't been incremented
success
AtomicInteger counter = new AtomicInteger(1);
Consumer<AtomicInteger> increment = c -> c.incrementAndGet();
ConditionalConsumer.builder(increment)
.acceptIf(c -> c.get() == 1) // check condition
.accept(counter);
int result = counter.get(); // result is 2 because it has been incremented
this interface is useful when performing operations that may be not processed under certain circumstances.
This library internally uses logback as logging library. To manage logging in your application just follow logback documentation available here.