[
  {
    "path": ".gitignore",
    "content": "target\n*.iml\n.idea\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: java\ninstall: mvn install -DskipTests=true -Dgpg.skip=true\njdk:\n  - oraclejdk8\nafter_success:\n  - mvn clean test jacoco:report coveralls:jacoco"
  },
  {
    "path": "README.md",
    "content": "[![Build Status](https://travis-ci.org/nurkiewicz/async-retry.svg?branch=master)](https://travis-ci.org/nurkiewicz/async-retry) [![Build Status](https://drone.io/github.com/nurkiewicz/async-retry/status.png)](https://drone.io/github.com/nurkiewicz/async-retry/latest) [![Coverage Status](https://img.shields.io/coveralls/nurkiewicz/async-retry.svg)](https://coveralls.io/r/nurkiewicz/async-retry) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.nurkiewicz.asyncretry/asyncretry/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.nurkiewicz.asyncretry/asyncretry)\n\n# Asynchronous retry pattern\n\nWhen you have a piece of code that often fails and must be retried, this Java 7/8 library provides rich and unobtrusive API with fast and scalable solution to this problem:\n\n```java\nScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();\nRetryExecutor executor = new AsyncRetryExecutor(scheduler).\n\tretryOn(SocketException.class).\n\twithExponentialBackoff(500, 2).     //500ms times 2 after each retry\n\twithMaxDelay(10_000).               //10 seconds\n\twithUniformJitter().                //add between +/- 100 ms randomly\n\twithMaxRetries(20);\n```\n\nYou can now run arbitrary block of code and the library will retry it for you in case it throws `SocketException`:\n\n```java\nfinal CompletableFuture<Socket> future = executor.getWithRetry(() ->\n\t\tnew Socket(\"localhost\", 8080)\n);\n\nfuture.thenAccept(socket ->\n\t\tSystem.out.println(\"Connected! \" + socket)\n);\n```\n\nPlease look carefully! `getWithRetry()` does not block. It returns `CompletableFuture` immediately and invokes given function asynchronously. You can listen for that `Future` or even for multiple futures at once and do other work in the meantime. So what this code does is: trying to connect to `localhost:8080` and if it fails with `SocketException` it will retry after 500 milliseconds (with some random jitter), doubling delay after each retry, but not above 10 seconds.\n\nEquivalent but more concise syntax:\n\n```java\nexecutor.\n\t\tgetWithRetry(() -> new Socket(\"localhost\", 8080)).\n\t\tthenAccept(socket -> System.out.println(\"Connected! \" + socket));\n```\n\nThis is a sample output that you might expect:\n\n    TRACE | Retry 0 failed after 3ms, scheduled next retry in 508ms (Sun Jul 21 21:01:12 CEST 2013)\n    java.net.ConnectException: Connection refused\n    \tat java.net.PlainSocketImpl.socketConnect(Native Method) ~[na:1.8.0-ea]\n    \t//...\n    \n    TRACE | Retry 1 failed after 0ms, scheduled next retry in 934ms (Sun Jul 21 21:01:13 CEST 2013)\n    java.net.ConnectException: Connection refused\n    \tat java.net.PlainSocketImpl.socketConnect(Native Method) ~[na:1.8.0-ea]\n    \t//...\n    \n    TRACE | Retry 2 failed after 0ms, scheduled next retry in 1919ms (Sun Jul 21 21:01:15 CEST 2013)\n    java.net.ConnectException: Connection refused\n    \tat java.net.PlainSocketImpl.socketConnect(Native Method) ~[na:1.8.0-ea]\n    \t//...\n    \n    TRACE | Successful after 2 retries, took 0ms and returned: Socket[addr=localhost/127.0.0.1,port=8080,localport=46332]\n\n    Connected! Socket[addr=localhost/127.0.0.1,port=8080,localport=46332]\n    \nImagine you connect to two different systems, one is *slow*, second *unreliable* and fails often:\n\n```java\nCompletableFuture<String> stringFuture = executor.getWithRetry(ctx -> unreliable());\nCompletableFuture<Integer> intFuture = executor.getWithRetry(ctx -> slow());\n\nstringFuture.thenAcceptBoth(intFuture, (String s, Integer i) -> {\n\t//both done after some retries\n});\n```\n\n`thenAcceptBoth()` callback is executed asynchronously when both slow and unreliable systems finally reply without any failure. Similarly (using `CompletableFuture.acceptEither()`) you can call two or more unreliable servers asynchronously at the same time and be notified when the first one succeeds after some number of retries.\n\nI can't emphasize this enough - retries are executed asynchronously and effectively use thread pool, rather than sleeping blindly.\n\n## Rationale\n\nOften we are forced to [retry](http://servicedesignpatterns.com/WebServiceInfrastructures/IdempotentRetry) given piece of code because it failed and we must try again, typically with a small delay to spare CPU. This requirement is quite common and there are few ready-made generic implementations with [retry support in Spring Batch](http://static.springsource.org/spring-batch/reference/html/retry.html) through [`RetryTemplate`](http://static.springsource.org/spring-batch/2.1.x/apidocs/org/springframework/batch/retry/support/RetryTemplate.html) class being best known. But there are few other, quite similar approaches ([[1]](http://fahdshariff.blogspot.no/2009/08/retrying-operations-in-java.html), [[2]](https://github.com/Ninja-Squad/ninja-core/tree/master/src/main/java/com/ninja_squad/core/retry)). All of these attempts (and I bet many of you implemented similar tool yourself!) suffer the same issue - they are blocking, thus wasting a lot of resources and not scaling well.\n\nThis is not bad *per se* because it makes programming model much simpler - the library takes care of retrying and you simply have to wait for return value longer than usual. But not only it creates leaky abstraction (method that is typically very fast suddenly becomes slow due to retries and delay), but also wastes valuable threads since such facility will spend most of the time sleeping between retries. Therefore [`Async-Retry`](https://github.com/nurkiewicz/async-retry) utility was created, targeting **Java 8** (with [Java 7 backport](https://github.com/nurkiewicz/async-retry/tree/java7) existing) and addressing issues above.\n\nThe main abstraction is [`RetryExecutor`](https://github.com/nurkiewicz/async-retry/blob/master/src/main/java/com/nurkiewicz/asyncretry/RetryExecutor.java) that provides simple API:\n\n```java\npublic interface RetryExecutor {\n\n\tCompletableFuture<Void> doWithRetry(RetryRunnable action);\n\n\t<V> CompletableFuture<V> getWithRetry(Callable<V> task);\n\n\t<V> CompletableFuture<V> getWithRetry(RetryCallable<V> task);\n\n\t<V> CompletableFuture<V> getFutureWithRetry(RetryCallable<CompletableFuture<V>> task);\n}\n```\n\nDon't worry about [`RetryRunnable`](https://github.com/nurkiewicz/async-retry/blob/master/src/main/java/com/nurkiewicz/asyncretry/function/RetryRunnable.java) and [`RetryCallable`](https://github.com/nurkiewicz/async-retry/blob/master/src/main/java/com/nurkiewicz/asyncretry/function/RetryCallable.java) - they allow checked exceptions for your convenience and most of the time we will use lambda expressions anyway.\n\nPlease note that it returns [`CompletableFuture`](http://nurkiewicz.blogspot.no/2013/05/java-8-definitive-guide-to.html). We no longer pretend that calling faulty method is fast. If the library encounters an exception it will retry our block of code with preconfigured backoff delays. The invocation time will sky-rocket from milliseconds to several seconds. `CompletableFuture` clearly indicates that. Moreover it's not a dumb [`java.util.concurrent.Future`](http://nurkiewicz.blogspot.no/2013/02/javautilconcurrentfuture-basics.html) we all know - [`CompletableFuture` in Java 8 is very powerful](http://nurkiewicz.blogspot.no/2013/05/java-8-completablefuture-in-action.html) and most importantly - non-blocking by default.\n\nIf you need blocking result after all, just call `.get()` on `Future` object.\n\n## Basic API\n\nThe API is very simple. You provide a block of code and the library will run it multiple times until it returns normally rather than throwing an exception. It may also put configurable delays between retries:\n\n```java\nRetryExecutor executor = //see \"Creating an instance of RetryExecutor\" below\n\nexecutor.getWithRetry(() -> new Socket(\"localhost\", 8080));\n```\n\nReturned `CompletableFuture<Socket>` will be resolved once connecting to `localhost:8080` succeeds. Optionally we can consume [`RetryContext`](https://github.com/nurkiewicz/async-retry/blob/master/src/main/java/com/nurkiewicz/asyncretry/RetryContext.java) to get extra context like which retry is currently being executed:\n\n```java\nexecutor.\n\tgetWithRetry(ctx -> new Socket(\"localhost\", 8080 + ctx.getRetryCount())).\n\tthenAccept(System.out::println);\n```\n\nThis code is more clever than it looks. During first execution `ctx.getRetryCount()` returns `0`, therefore we try to connect to `localhost:8080`. If this fails, next retry will try `localhost:8081` (`8080 + 1`) and so on. And if you realize that all of this happens asynchronously you can scan ports of several machines and be notified about first responding port on each host:\n\n```java\nArrays.asList(\"host-one\", \"host-two\", \"host-three\").\n\tstream().\n\tforEach(host ->\n\t\texecutor.\n\t\t\tgetWithRetry(ctx -> new Socket(host, 8080 + ctx.getRetryCount())).\n\t\t\tthenAccept(System.out::println)\n\t);\n```\n\nFor each host `RetryExecutor` will attempt to connect to port 8080 and retry with higher ports. \n\n`getFutureWithRetry()` requires special attention. I you want to retry method that already returns `CompletableFuture<V>`: e.g. result of asynchronous HTTP call:\n\n```java\nprivate CompletableFuture<String> asyncHttp(URL url) { /*...*/}\n\n//...\n\nfinal CompletableFuture<CompletableFuture<String>> response =\n\texecutor.getWithRetry(ctx ->\n\t\tasyncHttp(new URL(\"http://example.com\")));\n```\n\nPassing `asyncHttp()` to `getWithRetry()` will yield `CompletableFuture<CompletableFuture<V>>`. Not only it's awkward to work with, but also broken. The library will barely call `asyncHttp()` and retry only if it fails, but not if returned `CompletableFuture<String>` fails. The solution is simple:\n\n```java\nfinal CompletableFuture<String> response =\n\texecutor.getFutureWithRetry(ctx ->\n\t\tasyncHttp(new URL(\"http://example.com\")));\n```\n\nIn this case `RetryExecutor` will understand that whatever was returned from `asyncHttp()` is actually just a `Future` and will (asynchronously) wait for result or failure. Speaking of which, in some cases despite retrying `RetryExecutor` will fail to obtain successful result. In general there are three possible outcomes of returned `CompletableFuture`:\n\n1. *successful* - (possibly after some number of retries) - when our function eventually returns rather than throws\n\n2. *exceptional* due to excessive retries - if you configure finite number of retries, `RetryExecutor` will eventually give up. In that case `Future` is completed exceptionally, providing last encountered exception\n\n3. *exceptional* due to exception that should not be retried (see e.g. `abortOn()` and `abortIf()`) - just as above last encountered exception completes `Future`.\n\nYou can handle all these cases with the following by using [`CompletableFuture.whenComplete()`](http://download.java.net/lambda/b102/docs/api/java/util/concurrent/CompletableFuture.html#whenComplete(java.util.function.BiConsumer)) or [`CompletableFuture.handle()`](http://download.java.net/lambda/b102/docs/api/java/util/concurrent/CompletableFuture.html#handle(java.util.function.BiFunction)):\n\n```java\nexecutor.\n\tgetWithRetry(() -> new Socket(\"localhost\", 8080)).\n\twhenComplete((socket, error) -> {\n\t\tif (socket != null) {\n\t\t\t//connected OK, proceed\n\t\t} else {\n\t\t\tlog.error(\"Can't connect, last error:\", error);\n\t\t}\n\t});\n```\n\n## Configuration options\n\nIn general there are two important factors you can configure: [`RetryPolicy`](https://github.com/nurkiewicz/async-retry/blob/master/src/main/java/com/nurkiewicz/asyncretry/policy/RetryPolicy.java) that controls whether next retry attempt should be made and [`Backoff`](https://github.com/nurkiewicz/async-retry/blob/master/src/main/java/com/nurkiewicz/asyncretry/backoff/Backoff.java) - that optionally adds delay between subsequent retry attempts.\n\nBy default `RetryExecutor` repeats user task infinitely on every `\tThrowable` and adds 1 second delay between retry attempts.\n\n### Creating an instance of `RetryExecutor`\n\nDefault implementation of `RetryExecutor` is [`AsyncRetryExecutor`](https://github.com/nurkiewicz/async-retry/blob/master/src/main/java/com/nurkiewicz/asyncretry/AsyncRetryExecutor.java) which you can create directly:\n\n```java\nScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();\n\nRetryExecutor executor = new AsyncRetryExecutor(scheduler);\n\n//...\n\nscheduler.shutdownNow();\n```\n\nThe only required dependency is standard [`ScheduledExecutorService` from JDK](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ScheduledExecutorService.html). One thread is enough in many cases but if you want to concurrently handle retries of hundreds or more tasks, consider increasing the pool size.\n\nNotice that the `AsyncRetryExecutor` does not take care of shutting down the `ScheduledExecutorService`. This is a conscious design decision which will be explained later.\n\n`AsyncRetryExecutor` has few other constructors but most of the time altering the behaviour of this class is most convenient with calling chained `with*()` methods. You will see plenty of examples written this way. Later on we will simply use `executor` reference without defining it. Assume it's of `RetryExecutor` type.\n\n### Retrying policy\n\n#### Exception classes\n\nBy default every `Throwable` (except special `AbortRetryException`) thrown from user task causes retry. Obviously this is configurable. For example in JPA you may want to retry a transaction that failed due to [`OptimisticLockException`](http://docs.oracle.com/javaee/6/api/javax/persistence/OptimisticLockException.html) - but every other exception should fail immediately:\n\n```java\nexecutor.\n\tretryOn(OptimisticLockException.class).\n\twithNoDelay().\n\tgetWithRetry(ctx -> dao.optimistic());\n```\n\nWhere `dao.optimistic()` may throw `OptimisticLockException`. In that case you probably don't want any delay between retries, more on that later. If you don't like the default of retrying on every `Throwable`, just restrict that using `retryOn()`:\n\n```java\nexecutor.retryOn(Exception.class)\n```\n\nOf course the opposite might also be desired - to abort retrying and fail immediately in case of certain exception being thrown rather than retrying. It's that simple:\n\n```java\nexecutor.\n\tabortOn(NullPointerException.class).\n\tabortOn(IllegalArgumentException.class).\n\tgetWithRetry(ctx -> dao.optimistic());\n```\n\nClearly you don't want to retry `NullPointerException` or `IllegalArgumentException` as they indicate programming bug rather than transient failure. And finally you can combine both retry and abort policies. User code will retry in case of any `retryOn()` exception (or subclass) unless it should `abortOn()` specified exception. For example we want to retry every `IOException` or `SQLException` but abort if `FileNotFoundException` or `java.sql.DataTruncation` is encountered (order of declarations is irrelevant):\n\n```java\nexecutor.\n\tretryOn(IOException.class).\n\tabortOn(FileNotFoundException.class).\n\tretryOn(SQLException.class).\n\tabortOn(DataTruncation.class).\n\tgetWithRetry(ctx -> dao.load(42));\n```\n#### Exception predicates\n\nIf this is not enough you can provide custom predicate that will be invoked on each failure:\n\n```java\nexecutor.\n\tabortIf(throwable ->\n\t\tthrowable instanceof SQLException &&\n\t\t\t\tthrowable.getMessage().contains(\"ORA-00911\")\t\n\t).\n\tretryIf(t -> t.getCause() != null);\n```\n\nIf any of `abortIf()` or `retryIf()` predicates return `true` task is aborted or retried respectively. Keep in mind that `abortIf()`/`retryIf()` take priority over `abortOn()`/`retryOn()` thus the following piece of code will retry on `FileNotFoundException(\"Access denied\")`:\n\n```java\nexecutor.\n\t\tabortOn(FileNotFoundException.class).\n\t\tabortOn(NullPointerException.class).\n\t\tretryIf(e -> e.getMessage().contains(\"denied\"))\n``` \n\nIf more than one `abortIf()` predicate passes as well as more than one `retryIf()` predicate then computation is aborted.\n\n#### Max number of retries\n\nAnother way of interrupting retrying \"loop\" (remember that this process is asynchronous, there is no blocking *loop*) is by specifying maximum number of retries:\n\n```java\nexecutor.withMaxRetries(5)\n```\n\nIn rare cases you may want to disable retries and barely take advantage from asynchronous execution. In that case try:\n\n```java\nexecutor.dontRetry()\n```\n\nMax number of retries takes precedence over `*On()` and `*If()` family of methods.\n\n### Delays between retries (backoff)\n\nRetrying immediately after failure is sometimes desired (see `OptimisticLockException` example) but in most cases it's a bad idea. If you can't connect to external system, waiting a little bit before next attempt sounds reasonably. You save CPU, bandwidth and other server's resources. But there are quite a few options to consider: \n\n* should we retry with constant intervals or [increase delay after each failure](http://en.wikipedia.org/wiki/Exponential_backoff)?\n\n* should there be a lower and upper limit on waiting time?\n\n* should we add random \"jitter\" to delay times to spread retries of many tasks in time?\n\nThis library answers all these questions.\n\n#### Fixed interval between retries\n\nBy default each retry is preceded by 1 second waiting time. So if initial attempt fails, first retry will be executed after 1 second. Of course we can change that default, e.g. to 200 milliseconds:\n\n```java\nexecutor.withFixedBackoff(200)\n```\n\nIf we are already here, by default backoff is applied after executing user task. If user task itself consumes some time, retries will be less frequent. For example with retry delay of 200ms and average time it takes before user task fails at about 50ms `RetryExecutor` will retry about 4 times per second (50ms + 200ms). However if you want to keep retry frequency at more predictable level you can use `fixedRate` flag:\n\n```java\nexecutor.\n\twithFixedBackoff(200).\n\twithFixedRate()\n```\n\nThis is similar to \"fixed rate\" vs. \"fixed delay\" approaches in [`ScheduledExecutorService`](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ScheduledExecutorService.html). BTW don't expect `RetryExecutor` to be very precise, it does it's best but it heavily depends on aforementioned `ScheduledExecutorService` accuracy.\n\n#### Exponentially growing intervals between retries\n\nIt's probably an active research subject, but in general you may wish to expand retry delay over time, assuming that if the user task fails several times we should try less frequently. For example let's say we start with 100ms delay until first retry attempt is made but if that one fails as well, we should wait two times more (200ms). And later 400ms, 800ms... You get the idea:\n\n```java\nexecutor.withExponentialBackoff(100, 2)\n```\n\nThis is an exponential function that can grow very fast. Thus it's useful to set maximum backoff time at some reasonable level, e.g. 10 seconds:\n\n```java\nexecutor.\n\twithExponentialBackoff(100, 2).\n\twithMaxDelay(10_000)      //10 seconds\n```\n\n#### Random jitter\n\nOne phenomena often observed during major outages is that systems tend to synchronize. Imagine a busy system that suddenly stops responding. Hundreds or thousands of requests fail and are retried. It depends on your backoff but by default all these requests will retry exactly after one second producing huge wave of traffic at one point in time. Finally such failures are propagated to other systems that, in turn, synchronize as well.\n\nTo avoid this problem it's useful to spread retries over time, flattening the load. A simple solution is to add random jitter to delay time so that not all request are scheduled for retry at the exact same time. You have choice between uniform jitter (random value from -100ms to 100ms):\n\n```java\nexecutor.withUniformJitter(100)     //ms\n```\n\n...and proportional jitter, multiplying delay time by random factor, by default between 0.9 and 1.1 (10%):\n\n```java\nexecutor.withProportionalJitter(0.1)        //10%\n```\n\nYou may also put hard lower limit on delay time to avoid to short retry times being scheduled:\n\n```java\nexecutor.withMinDelay(50)   //ms\n```\n\n#### No initial delay\n\nThere are cases when you want to run first retry immediately and fall back to configured backoff mechanism for subsequent requests. You can use `firstRetryNoDelay()` method:\n\n```java\nexecutor\n\t.withExponentialBackoff(100, 2)\n\t.firstRetryNoDelay();\nCompletableFuture<String> future = executor.getWithRetry(this::someTask);\n```\n\nWith the above configuration first retry is executed immediately, second retry after 100 ms, third after 200 ms and so on. Without `firstRetryNoDelay()` first retry would be delayed by 100 ms already.\n\n## Implementation details\n\nThis library was built with Java 8 in mind to take advantage of lambdas and new `CompletableFuture` abstraction (but [Java 7 port with Guava dependency exists](https://github.com/nurkiewicz/async-retry/tree/java7)). It uses `ScheduledExecutorService` underneath to run tasks and schedule retries in the future - which allows best thread utilization.\n\nBut what is really interesting is that the whole library is fully immutable, there is no single mutable field, at all. This might be counter-intuitive at first, take for example this trivial code sample:\n\n```java\nScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();\n\nAsyncRetryExecutor first = new AsyncRetryExecutor(scheduler).\n\tretryOn(Exception.class).\n\twithExponentialBackoff(500, 2);\n\nAsyncRetryExecutor second = first.abortOn(FileNotFoundException.class);\n\nAsyncRetryExecutor third = second.withMaxRetries(10);\n```\n\nIt might seem that all `with*()` methods or `retryOn()`/`abortOn()` mutate existing executor. But that's not the case, each configuration change **creates new instance**, leaving the old one untouched. So for example while `first` executor will retry on `FileNotFoundException`, the `second` and `third` won't. However they all share the same `scheduler`. This is the reason why `AsyncRetryExecutor` does not shut down `ScheduledExecutorService` (it doesn't even have any `close()` method). Since we have no idea how many copies of `AsyncRetryExecutor` exist pointing to the same scheduler, we don't even try to manage its lifecycle. However this is typically not a problem (see *Spring integration* below).\n\nYou might be wondering, why such an awkward design decision? There are three reasons:\n\n* when writing a concurrent code immutability can greatly reduce risk of multi-threading bugs. For example `RetryContext` holds number of retries. But instead of mutating it we simply create new instance (copy) with incremented but `final` counter. No race condition or visibility can ever occur.\n\n* if you are given an existing `RetryExecutor` which is almost exactly what you want but you need one minor tweak, you simply call `executor.with...()` and get a fresh copy. You don't have to worry about other places where the same executor was used (see: *Spring integration* for further examples)\n\n* functional programming and immutable data structures are *sexy* these days ;-).\n\nN.B.: `AsyncRetryExecutor` is **not** marked `final`, does you can break immutability by subclassing it and adding mutable state. Please don't do this, subclassing is only permitted to alter behaviour.\n\n## Dependencies\n\nThis library requires Java 8 and [SLF4J](http://www.slf4j.org/) for logging. Java 7 port additionally depends on [Guava](http://code.google.com/p/guava-libraries/).\n\n## Spring integration\n\nIf you are just about to use `RetryExecutor` in Spring - feel free, but the configuration API might not work for you. Spring promotes (or used to promote) the convention of mutable services with plenty of setters. In XML you define bean and invoke setters (via `<property name=\"...\"/>`) on it. This convention assumes the existence of mutating setters. But I found this approach error-prone and counter-intuitive under some circumstances.\n\nLet's say we globally defined [`org.springframework.transaction.support.TransactionTemplate`](http://static.springsource.org/spring/docs/current/javadoc-api/org/springframework/transaction/support/TransactionTemplate.html) bean and injected it in multiple places. Great. Now there is this one single request that requires slightly different timeout:\n\n```java\n@Autowired\nprivate TransactionTemplate template;\n```\n\nand later in the same class:\n    \n```java\nfinal int oldTimeout = template.getTimeout();\ntemplate.setTimeout(10_000);\n//do the work\ntemplate.setTimeout(oldTimeout);\n```\n\nThis code is wrong on so many levels! First of all if something fails we never restore `oldTimeout`. OK, `finally` to the rescue. But also notice how we changed global, shared `TransactionTemplate` instance. Who knows how many other beans and threads are just about to use it, unaware of changed configuration?\n\nAnd even if you do want to globally change the transaction timeout, fair enough, but it's still wrong way to do this. `private timeout` field is not `volatile` and thus changes made to it may or may not be visible to other threads. What a mess! The same problem appears with many other classes like [`JmsTemplate`](http://static.springsource.org/spring/docs/3.2.x/javadoc-api/org/springframework/jms/core/JmsTemplate.html).\n\nYou see where I'm going? Just create one, immutable service class and safely adjust it by creating copies whenever you need it. And using such services is equally simple these days:\n\n```java\n@Configuration\nclass Beans {\n\n\t@Bean\n\tpublic RetryExecutor retryExecutor() {\n\t\treturn new AsyncRetryExecutor(scheduler()).\n\t\t\tretryOn(SocketException.class).\n\t\t\twithExponentialBackoff(500, 2);\n\t}\n\n\t@Bean(destroyMethod = \"shutdownNow\")\n\tpublic ScheduledExecutorService scheduler() {\n\t\treturn Executors.newSingleThreadScheduledExecutor();\n\t}\n\n}\n```\n\nHey! It's 21st century, we don't need XML in Spring any more. Bootstrap is simple as well:\n\n```java\nfinal ApplicationContext context = new AnnotationConfigApplicationContext(Beans.class);\nfinal RetryExecutor executor = context.getBean(RetryExecutor.class);\n//...\ncontext.close();\n```\n\nAs you can see integrating modern, immutable services with Spring is just as simple. BTW if you are not prepared for such a big change when designing your own services, at least consider [constructor injection](http://nurkiewicz.blogspot.no/2011/09/evolution-of-spring-dependency.html).\n\n## Maturity\n\nThis library is covered with a strong battery of unit tests ([![Build Status](https://travis-ci.org/nurkiewicz/async-retry.svg?branch=master)](https://travis-ci.org/nurkiewicz/async-retry)). However it wasn't yet used in any production code and the API is subject to change. Of course you are encouraged to submit [bugs, feature requests](https://github.com/nurkiewicz/async-retry/issues) and [pull requests](https://github.com/nurkiewicz/async-retry/pulls). It was developed with Java 8 in mind but [Java 7 backport](https://github.com/nurkiewicz/async-retry/tree/java7) exists with slightly more verbose API and mandatory Guava dependency ([`ListenableFuture`](http://nurkiewicz.blogspot.no/2013/02/listenablefuture-in-guava.html) instead of [`CompletableFuture` from Java 8](http://nurkiewicz.blogspot.no/2013/05/java-8-definitive-guide-to.html)).\n\n## Using\n\n### Maven\n\nThis library is available in [Maven Central Repository](http://search.maven.org):\n\n```xml\n<dependency>\n    <groupId>com.nurkiewicz.asyncretry</groupId>\n    <artifactId>asyncretry</artifactId>\n    <version>0.0.7</version>\n</dependency>\n```\n\n\n### Maven (Java 7)\n\nBecause backport to Java 7 has different API, it is maintained in a [separate branch](https://github.com/nurkiewicz/async-retry/tree/java7). It is also deployed to Maven Central under:\n\n```xml\n<dependency>\n    <groupId>com.nurkiewicz.asyncretry</groupId>\n    <artifactId>asyncretry-jdk7</artifactId>\n    <version>0.0.7</version>\n</dependency>\n```\n\n### Troubleshooting: `invalid target release: 1.8` during maven build\n\nIf you see this error message during maven build:\n\n\t[INFO] BUILD FAILURE\n\t...\n\t[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.1:compile (default-compile) on project lazyseq: \n\tFatal error compiling: invalid target release: 1.8 -> [Help 1]\n\nit means you are not compiling using Java 8. [Download JDK 8 with lambda support](https://jdk8.java.net/lambda/) and let maven use it:\n\n\t$ export JAVA_HOME=/path/to/jdk8\n\n\n## Version history\n\n### 0.0.7 (24-05-2015)\n\n* [`firstRetryNoDelay()` method](https://github.com/nurkiewicz/async-retry/issues/6)\n* [Add withNoRetries() and withInfiniteRetries()](https://github.com/nurkiewicz/async-retry/issues/5)\n* Fixed [*Errors from predicates are silently ignored*](https://github.com/nurkiewicz/async-retry/pull/7)\n\n### 0.0.6 (26-11-2014)\n\n* [`SyncRetryExecutor`](https://github.com/nurkiewicz/async-retry/blob/0.0.6/src/main/java/com/nurkiewicz/asyncretry/SyncRetryExecutor.java) convenience class.\n\n### 0.0.5 (31-05-2014)\n\n* Bringing back Java 7 support\n\n### 0.0.4 (30-05-2014)\n\n* First official release into [Maven Central repository](http://central.maven.org/maven2/com/nurkiewicz/asyncretry/asyncretry).\n\n### 0.0.3 (05-01-2014)\n\n* Fixed [#3 *RetryOn ignored due to wrong command order*](https://github.com/nurkiewicz/async-retry/issues/3)\n* `AbortRetryException` class was moved from `com.nurkiewicz.asyncretry.policy.exception` to `com.nurkiewicz.asyncretry.policy`\n* Java 7 backport is no longer maintained starting from this version\n\n### 0.0.2 (28-07-2013)\n\n* Ability to specify multiple exception classes in `retryOn()`/`abortON()` using varargs\n\n### 0.0.1 (23-07-2013)\n\n* Initial revision\n"
  },
  {
    "path": "license.txt",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n\t<modelVersion>4.0.0</modelVersion>\n\t<parent>\n\t\t<groupId>org.sonatype.oss</groupId>\n\t\t<artifactId>oss-parent</artifactId>\n\t\t<version>7</version>\n\t</parent>\n\t<name>Async-Retry</name>\n\t<groupId>com.nurkiewicz.asyncretry</groupId>\n\t<artifactId>asyncretry</artifactId>\n\t<version>0.0.8-SNAPSHOT</version>\n\t<packaging>jar</packaging>\n\t<description>Library that asynchronously retries failed invocations of arbitrary code</description>\n\t<url>https://github.com/nurkiewicz/async-retry</url>\n\t<licenses>\n\t\t<license>\n\t\t\t<name>Apache License, Version 2.0</name>\n\t\t\t<url>http://www.apache.org/licenses/LICENSE-2.0</url>\n\t\t</license>\n\t</licenses>\n\n\t<properties>\n\t\t<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n\t</properties>\n\n\t<scm>\n\t\t<connection>scm:git:git@github.com:nurkiewicz/async-retry.git</connection>\n\t\t<url>scm:git:git@github.com:nurkiewicz/async-retry.git</url>\n\t\t<developerConnection>scm:git:git@github.com:nurkiewicz/async-retry.git</developerConnection>\n\t\t<tag>HEAD</tag>\n\t</scm>\n\n\t<developers>\n\t\t<developer>\n\t\t\t<name>Tomasz Nurkiewicz</name>\n\t\t\t<url>http://nurkiewicz.com</url>\n\t\t</developer>\n\t</developers>\n\n\t<distributionManagement>\n\t\t<repository>\n\t\t\t<id>sonatype-nexus-staging</id>\n\t\t\t<name>Nexus Release Repository</name>\n\t\t\t<url>http://oss.sonatype.org/service/local/staging/deploy/maven2/</url>\n\t\t</repository>\n\t</distributionManagement>\n\n\t<dependencies>\n\n\t\t<dependency>\n\t\t\t<groupId>org.slf4j</groupId>\n\t\t\t<artifactId>slf4j-api</artifactId>\n\t\t\t<version>1.7.5</version>\n\t\t</dependency>\n\n\t\t<!-- Testing -->\n\t\t<dependency>\n\t\t\t<groupId>org.testng</groupId>\n\t\t\t<artifactId>testng</artifactId>\n\t\t\t<version>6.8.1</version>\n\t\t\t<exclusions>\n\t\t\t\t<exclusion>\n\t\t\t\t\t<groupId>junit</groupId>\n\t\t\t\t\t<artifactId>junit</artifactId>\n\t\t\t\t</exclusion>\n\t\t\t</exclusions>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\n\t\t<dependency>\n\t\t\t<groupId>org.assertj</groupId>\n\t\t\t<artifactId>assertj-core</artifactId>\n\t\t\t<version>1.6.0</version>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.mockito</groupId>\n\t\t\t<artifactId>mockito-all</artifactId>\n\t\t\t<version>1.9.5</version>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>commons-lang</groupId>\n\t\t\t<artifactId>commons-lang</artifactId>\n\t\t\t<version>2.6</version>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>ch.qos.logback</groupId>\n\t\t\t<artifactId>logback-classic</artifactId>\n\t\t\t<version>1.0.7</version>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\n\t</dependencies>\n\n\t<build>\n\t\t<plugins>\n\t\t\t<plugin>\n\t\t\t\t<artifactId>maven-compiler-plugin</artifactId>\n\t\t\t\t<version>3.1</version>\n\t\t\t\t<configuration>\n\t\t\t\t\t<source>1.8</source>\n\t\t\t\t\t<target>1.8</target>\n\t\t\t\t</configuration>\n\t\t\t</plugin>\n\t\t\t<plugin>\n\t\t\t\t<artifactId>maven-release-plugin</artifactId>\n\t\t\t\t<version>2.5.1</version>\n\t\t\t\t<configuration>\n\t\t\t\t\t<tagNameFormat>@{project.version}</tagNameFormat>\n\t\t\t\t\t<autoVersionSubmodules>true</autoVersionSubmodules>\n\t\t\t\t\t<goals>deploy</goals>\n\t\t\t\t</configuration>\n\t\t\t</plugin>\n\t\t\t<plugin>\n\t\t\t\t<artifactId>maven-source-plugin</artifactId>\n\t\t\t\t<version>2.2.1</version>\n\t\t\t\t<executions>\n\t\t\t\t\t<execution>\n\t\t\t\t\t\t<id>attach-sources</id>\n\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t<goal>jar</goal>\n\t\t\t\t\t\t</goals>\n\t\t\t\t\t</execution>\n\t\t\t\t</executions>\n\t\t\t</plugin>\n\t\t\t<plugin>\n\t\t\t\t<artifactId>maven-javadoc-plugin</artifactId>\n\t\t\t\t<version>2.9.1</version>\n\t\t\t\t<executions>\n\t\t\t\t\t<execution>\n\t\t\t\t\t\t<id>attach-javadocs</id>\n\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t<goal>jar</goal>\n\t\t\t\t\t\t</goals>\n\t\t\t\t\t</execution>\n\t\t\t\t</executions>\n\t\t\t</plugin>\n\t\t\t<plugin>\n\t\t\t\t<artifactId>maven-gpg-plugin</artifactId>\n\t\t\t\t<version>1.5</version>\n\t\t\t\t<executions>\n\t\t\t\t\t<execution>\n\t\t\t\t\t\t<id>sign-artifacts</id>\n\t\t\t\t\t\t<phase>verify</phase>\n\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t<goal>sign</goal>\n\t\t\t\t\t\t</goals>\n\t\t\t\t\t</execution>\n\t\t\t\t</executions>\n\t\t\t</plugin>\n\t\t\t<plugin>\n\t\t\t\t<groupId>org.sonatype.plugins</groupId>\n\t\t\t\t<artifactId>nexus-staging-maven-plugin</artifactId>\n\t\t\t\t<version>1.6.5</version>\n\t\t\t\t<extensions>true</extensions>\n\t\t\t\t<configuration>\n\t\t\t\t\t<serverId>sonatype-nexus-staging</serverId>\n\t\t\t\t\t<nexusUrl>https://oss.sonatype.org/</nexusUrl>\n\t\t\t\t\t<autoReleaseAfterClose>true</autoReleaseAfterClose>\n\t\t\t\t</configuration>\n\t\t\t</plugin>\n\t\t\t<plugin>\n\t\t\t\t<groupId>org.eluder.coveralls</groupId>\n\t\t\t\t<artifactId>coveralls-maven-plugin</artifactId>\n\t\t\t\t<version>2.2.0</version>\n\t\t\t</plugin>\n\t\t\t<plugin>\n\t\t\t\t<groupId>org.jacoco</groupId>\n\t\t\t\t<artifactId>jacoco-maven-plugin</artifactId>\n\t\t\t\t<version>0.7.2.201409121644</version>\n\t\t\t\t<executions>\n\t\t\t\t\t<execution>\n\t\t\t\t\t\t<id>prepare-agent</id>\n\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t<goal>prepare-agent</goal>\n\t\t\t\t\t\t</goals>\n\t\t\t\t\t</execution>\n\t\t\t\t</executions>\n\t\t\t</plugin>\n\t\t</plugins>\n\t</build>\n\n</project>\n"
  },
  {
    "path": "src/main/java/com/nurkiewicz/asyncretry/AsyncRetryContext.java",
    "content": "package com.nurkiewicz.asyncretry;\n\nimport com.nurkiewicz.asyncretry.policy.RetryPolicy;\n\nimport java.util.Objects;\n\npublic class AsyncRetryContext implements RetryContext {\n\n\tprivate final RetryPolicy retryPolicy;\n\tprivate final int retry;\n\tprivate final Throwable lastThrowable;\n\n\tpublic AsyncRetryContext(RetryPolicy retryPolicy) {\n\t\tthis(retryPolicy, 0, null);\n\t}\n\n\tpublic AsyncRetryContext(RetryPolicy retryPolicy, int retry, Throwable lastThrowable) {\n\t\tthis.retryPolicy = Objects.requireNonNull(retryPolicy);\n\t\tthis.retry = retry;\n\t\tthis.lastThrowable = lastThrowable;\n\t}\n\n\t@Override\n\tpublic boolean willRetry() {\n\t\treturn retryPolicy.shouldContinue(this.nextRetry(new Exception()));\n\t}\n\n\t@Override\n\tpublic int getRetryCount() {\n\t\treturn retry;\n\t}\n\n\t@Override\n\tpublic Throwable getLastThrowable() {\n\t\treturn lastThrowable;\n\t}\n\n\tpublic AsyncRetryContext nextRetry(Throwable cause) {\n\t\treturn new AsyncRetryContext(retryPolicy, retry + 1, cause);\n\t}\n\n\tpublic AsyncRetryContext prevRetry() {\n\t\treturn new AsyncRetryContext(retryPolicy, retry - 1, lastThrowable);\n\t}\n\n}\n"
  },
  {
    "path": "src/main/java/com/nurkiewicz/asyncretry/AsyncRetryExecutor.java",
    "content": "package com.nurkiewicz.asyncretry;\n\nimport com.nurkiewicz.asyncretry.backoff.Backoff;\nimport com.nurkiewicz.asyncretry.backoff.ExponentialDelayBackoff;\nimport com.nurkiewicz.asyncretry.backoff.FixedIntervalBackoff;\nimport com.nurkiewicz.asyncretry.function.RetryCallable;\nimport com.nurkiewicz.asyncretry.function.RetryRunnable;\nimport com.nurkiewicz.asyncretry.policy.RetryPolicy;\n\nimport java.util.Objects;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.ScheduledExecutorService;\nimport java.util.function.Predicate;\n\nimport static java.util.concurrent.TimeUnit.MILLISECONDS;\n\n/**\n * @author Tomasz Nurkiewicz\n * @since 7/15/13, 11:06 PM\n */\npublic class AsyncRetryExecutor implements RetryExecutor {\n\n\tprivate final ScheduledExecutorService scheduler;\n\tprivate final boolean fixedDelay;\n\tprivate final RetryPolicy retryPolicy;\n\tprivate final Backoff backoff;\n\n\tpublic AsyncRetryExecutor(ScheduledExecutorService scheduler) {\n\t\tthis(scheduler, RetryPolicy.DEFAULT, Backoff.DEFAULT);\n\t}\n\n\tpublic AsyncRetryExecutor(ScheduledExecutorService scheduler, Backoff backoff) {\n\t\tthis(scheduler, RetryPolicy.DEFAULT, backoff);\n\t}\n\n\tpublic AsyncRetryExecutor(ScheduledExecutorService scheduler, RetryPolicy retryPolicy) {\n\t\tthis(scheduler, retryPolicy, Backoff.DEFAULT);\n\t}\n\n\tpublic AsyncRetryExecutor(ScheduledExecutorService scheduler, RetryPolicy retryPolicy, Backoff backoff) {\n\t\tthis(scheduler, retryPolicy, backoff, false);\n\t}\n\n\tpublic AsyncRetryExecutor(ScheduledExecutorService scheduler, RetryPolicy retryPolicy, Backoff backoff, boolean fixedDelay) {\n\t\tthis.scheduler = Objects.requireNonNull(scheduler);\n\t\tthis.retryPolicy = Objects.requireNonNull(retryPolicy);\n\t\tthis.backoff = Objects.requireNonNull(backoff);\n\t\tthis.fixedDelay = fixedDelay;\n\t}\n\n\t@Override\n\tpublic CompletableFuture<Void> doWithRetry(RetryRunnable action) {\n\t\treturn getWithRetry(context -> {\n\t\t\taction.run(context);\n\t\t\treturn null;\n\t\t});\n\t}\n\n\t@Override\n\tpublic <V> CompletableFuture<V> getWithRetry(Callable<V> task) {\n\t\treturn getWithRetry(ctx -> task.call());\n\t}\n\n\t@Override\n\tpublic <V> CompletableFuture<V> getWithRetry(RetryCallable<V> task) {\n\t\treturn scheduleImmediately(createTask(task));\n\t}\n\n\t@Override\n\tpublic <V> CompletableFuture<V> getFutureWithRetry(RetryCallable<CompletableFuture<V>> task) {\n\t\treturn scheduleImmediately(createFutureTask(task));\n\t}\n\n\tprivate <V> CompletableFuture<V> scheduleImmediately(RetryJob<V> job) {\n\t\tscheduler.schedule(job, 0, MILLISECONDS);\n\t\treturn job.getFuture();\n\t}\n\n\tprotected <V> RetryJob<V> createTask(RetryCallable<V> function) {\n\t\treturn new SyncRetryJob<>(function, this);\n\t}\n\n\tprotected <V> RetryJob<V> createFutureTask(RetryCallable<CompletableFuture<V>> function) {\n\t\treturn new AsyncRetryJob<>(function, this);\n\t}\n\n\tpublic ScheduledExecutorService getScheduler() {\n\t\treturn scheduler;\n\t}\n\n\tpublic boolean isFixedDelay() {\n\t\treturn fixedDelay;\n\t}\n\n\tpublic RetryPolicy getRetryPolicy() {\n\t\treturn retryPolicy;\n\t}\n\n\tpublic Backoff getBackoff() {\n\t\treturn backoff;\n\t}\n\n\tpublic AsyncRetryExecutor withScheduler(ScheduledExecutorService scheduler) {\n\t\treturn new AsyncRetryExecutor(scheduler, retryPolicy, backoff, fixedDelay);\n\t}\n\n\tpublic AsyncRetryExecutor withRetryPolicy(RetryPolicy retryPolicy) {\n\t\treturn new AsyncRetryExecutor(scheduler, retryPolicy, backoff, fixedDelay);\n\t}\n\n\tpublic AsyncRetryExecutor withExponentialBackoff(long initialDelayMillis, double multiplier) {\n\t\tfinal ExponentialDelayBackoff backoff = new ExponentialDelayBackoff(initialDelayMillis, multiplier);\n\t\treturn new AsyncRetryExecutor(scheduler, retryPolicy, backoff, fixedDelay);\n\t}\n\n\tpublic AsyncRetryExecutor withFixedBackoff(long delayMillis) {\n\t\tfinal FixedIntervalBackoff backoff = new FixedIntervalBackoff(delayMillis);\n\t\treturn new AsyncRetryExecutor(scheduler, retryPolicy, backoff, fixedDelay);\n\t}\n\n\tpublic AsyncRetryExecutor withBackoff(Backoff backoff) {\n\t\treturn new AsyncRetryExecutor(scheduler, retryPolicy, backoff, fixedDelay);\n\t}\n\n\tpublic AsyncRetryExecutor withFixedRate() {\n\t\treturn new AsyncRetryExecutor(scheduler, retryPolicy, backoff, true);\n\t}\n\n\tpublic AsyncRetryExecutor withFixedRate(boolean fixedDelay) {\n\t\treturn new AsyncRetryExecutor(scheduler, retryPolicy, backoff, fixedDelay);\n\t}\n\n\t@SafeVarargs\n\tpublic final AsyncRetryExecutor retryOn(Class<? extends Throwable>... retryOnThrowables) {\n\t\treturn this.withRetryPolicy(retryPolicy.retryOn(retryOnThrowables));\n\t}\n\n\t@SafeVarargs\n\tpublic final AsyncRetryExecutor abortOn(Class<? extends Throwable>... abortOnThrowable) {\n\t\treturn this.withRetryPolicy(retryPolicy.abortOn(abortOnThrowable));\n\t}\n\n\tpublic AsyncRetryExecutor retryIf(Predicate<Throwable> retryPredicate) {\n\t\treturn this.withRetryPolicy(retryPolicy.retryIf(retryPredicate));\n\t}\n\n\tpublic AsyncRetryExecutor abortIf(Predicate<Throwable> abortPredicate) {\n\t\treturn this.withRetryPolicy(retryPolicy.abortIf(abortPredicate));\n\t}\n\n\tpublic AsyncRetryExecutor withUniformJitter() {\n\t\treturn this.withBackoff(this.backoff.withUniformJitter());\n\t}\n\n\tpublic AsyncRetryExecutor withUniformJitter(long range) {\n\t\treturn this.withBackoff(this.backoff.withUniformJitter(range));\n\t}\n\n\tpublic AsyncRetryExecutor withProportionalJitter() {\n\t\treturn this.withBackoff(this.backoff.withProportionalJitter());\n\t}\n\n\tpublic AsyncRetryExecutor withProportionalJitter(double multiplier) {\n\t\treturn this.withBackoff(this.backoff.withProportionalJitter(multiplier));\n\t}\n\n\tpublic AsyncRetryExecutor withMinDelay(long minDelayMillis) {\n\t\treturn this.withBackoff(this.backoff.withMinDelay(minDelayMillis));\n\t}\n\n\tpublic AsyncRetryExecutor withMaxDelay(long maxDelayMillis) {\n\t\treturn this.withBackoff(this.backoff.withMaxDelay(maxDelayMillis));\n\t}\n\n\tpublic AsyncRetryExecutor withMaxRetries(int times) {\n\t\treturn this.withRetryPolicy(this.retryPolicy.withMaxRetries(times));\n\t}\n\n\tpublic AsyncRetryExecutor dontRetry() {\n\t\treturn this.withRetryPolicy(this.retryPolicy.dontRetry());\n\t}\n\n\tpublic AsyncRetryExecutor retryInfinitely() {\n\t\treturn this.withMaxRetries(Integer.MAX_VALUE);\n\t}\n\n\tpublic AsyncRetryExecutor withNoDelay() {\n\t\treturn this.withBackoff(new FixedIntervalBackoff(0));\n\t}\n\n\tpublic AsyncRetryExecutor firstRetryNoDelay() {\n\t\treturn this.withBackoff(this.backoff.withFirstRetryNoDelay());\n\t}\n\n}\n"
  },
  {
    "path": "src/main/java/com/nurkiewicz/asyncretry/AsyncRetryJob.java",
    "content": "package com.nurkiewicz.asyncretry;\n\nimport com.nurkiewicz.asyncretry.function.RetryCallable;\n\nimport java.util.concurrent.CompletableFuture;\n\n/**\n * @author Tomasz Nurkiewicz\n * @since 7/21/13, 6:37 PM\n */\npublic class AsyncRetryJob<V> extends RetryJob<V> {\n\n\tprivate final RetryCallable<CompletableFuture<V>> userTask;\n\n\tpublic AsyncRetryJob(RetryCallable<CompletableFuture<V>> userTask, AsyncRetryExecutor parent) {\n\t\tthis(userTask, parent, new AsyncRetryContext(parent.getRetryPolicy()), new CompletableFuture<>());\n\t}\n\n\tpublic AsyncRetryJob(RetryCallable<CompletableFuture<V>> userTask, AsyncRetryExecutor parent, AsyncRetryContext context, CompletableFuture<V> future) {\n\t\tsuper(context, parent, future);\n\t\tthis.userTask = userTask;\n\t}\n\n\t@Override\n\tpublic void run(long startTime) {\n\t\ttry {\n\t\t\tuserTask.call(context).handle((result, throwable) -> {\n\t\t\t\tfinal long stopTime = System.currentTimeMillis() - startTime;\n\t\t\t\tif (throwable != null) {\n\t\t\t\t\thandleThrowable(throwable, stopTime);\n\t\t\t\t} else {\n\t\t\t\t\tcomplete(result, stopTime);\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t\t});\n\t\t} catch (Throwable t) {\n\t\t\thandleThrowable(t, System.currentTimeMillis() - startTime);\n\t\t}\n\t}\n\n\t@Override\n\tprotected RetryJob<V> nextTask(AsyncRetryContext nextRetryContext) {\n\t\treturn new AsyncRetryJob<>(userTask, parent, nextRetryContext, future);\n\t}\n\n\n}\n"
  },
  {
    "path": "src/main/java/com/nurkiewicz/asyncretry/RetryContext.java",
    "content": "package com.nurkiewicz.asyncretry;\n\n/**\n * @author Tomasz Nurkiewicz\n * @since 7/16/13, 6:36 PM\n */\npublic interface RetryContext {\n\tboolean willRetry();\n\n\t/**\n\t * Which retry is being executed right now\n\t * @return 1 means it's the first retry, i.e. action is executed for the second time\n\t */\n\tint getRetryCount();\n\n\tThrowable getLastThrowable();\n\n\tdefault boolean isFirstRetry() {\n\t\treturn getRetryCount() == 1;\n\t}\n\n}\n"
  },
  {
    "path": "src/main/java/com/nurkiewicz/asyncretry/RetryExecutor.java",
    "content": "package com.nurkiewicz.asyncretry;\n\nimport com.nurkiewicz.asyncretry.function.RetryCallable;\nimport com.nurkiewicz.asyncretry.function.RetryRunnable;\n\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.CompletableFuture;\n\n/**\n * @author Tomasz Nurkiewicz\n * @since 7/17/13, 7:25 PM\n */\npublic interface RetryExecutor {\n\n\tCompletableFuture<Void> doWithRetry(RetryRunnable action);\n\n\t<V> CompletableFuture<V> getWithRetry(Callable<V> task);\n\n\t<V> CompletableFuture<V> getWithRetry(RetryCallable<V> task);\n\n\t<V> CompletableFuture<V> getFutureWithRetry(RetryCallable<CompletableFuture<V>> task);\n}\n"
  },
  {
    "path": "src/main/java/com/nurkiewicz/asyncretry/RetryJob.java",
    "content": "package com.nurkiewicz.asyncretry;\n\nimport com.nurkiewicz.asyncretry.backoff.Backoff;\nimport com.nurkiewicz.asyncretry.policy.AbortRetryException;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.util.Date;\nimport java.util.concurrent.CompletableFuture;\n\nimport static java.util.concurrent.TimeUnit.MILLISECONDS;\n\n/**\n * @author Tomasz Nurkiewicz\n * @since 7/21/13, 6:10 PM\n */\npublic abstract class RetryJob<V> implements Runnable {\n\tprivate static final Logger log = LoggerFactory.getLogger(RetryJob.class);\n\tprotected final CompletableFuture<V> future;\n\tprotected final AsyncRetryContext context;\n\tprotected final AsyncRetryExecutor parent;\n\n\tpublic RetryJob(AsyncRetryContext context, AsyncRetryExecutor parent, CompletableFuture<V> future) {\n\t\tthis.context = context;\n\t\tthis.parent = parent;\n\t\tthis.future = future;\n\t}\n\n\tprotected void logSuccess(RetryContext context, V result, long duration) {\n\t\tlog.trace(\"Successful after {} retries, took {}ms and returned: {}\", context.getRetryCount(), duration, result);\n\t}\n\n\tprotected void handleManualAbort(AbortRetryException abortEx) {\n\t\tlogAbort(context);\n\t\tif (context.getLastThrowable() != null) {\n\t\t\tfuture.completeExceptionally(context.getLastThrowable());\n\t\t} else {\n\t\t\tfuture.completeExceptionally(abortEx);\n\t\t}\n\t}\n\n\tprotected void logAbort(RetryContext context) {\n\t\tlog.trace(\"Aborted by user after {} retries\", context.getRetryCount() + 1);\n\t}\n\n\tprotected void handleThrowable(Throwable t, long duration) {\n\t\tif (t instanceof AbortRetryException) {\n\t\t\thandleManualAbort((AbortRetryException) t);\n\t\t} else {\n\t\t\thandleUserThrowable(t, duration);\n\t\t}\n\t}\n\n\tprotected void handleUserThrowable(Throwable t, long duration) {\n\t\tfinal AsyncRetryContext nextRetryContext = context.nextRetry(t);\n\n\t\ttry {\n\t\t\tretryOrAbort(t, duration, nextRetryContext);\n\t\t} catch (Throwable predicateError) {\n\t\t\tlog.error(\"Threw while trying to decide on retry {} after {}\",\n\t\t\t\t\tnextRetryContext.getRetryCount(),\n\t\t\t\t\tduration,\n\t\t\t\t\tpredicateError);\n\t\t\tfuture.completeExceptionally(t);\n\t\t}\n\t}\n\n\tprivate void retryOrAbort(Throwable t, long duration, AsyncRetryContext nextRetryContext) {\n\t\tif (parent.getRetryPolicy().shouldContinue(nextRetryContext)) {\n\t\t\tfinal long delay = calculateNextDelay(duration, nextRetryContext, parent.getBackoff());\n\t\t\tretryWithDelay(nextRetryContext, delay, duration);\n\t\t} else {\n\t\t\tlogFailure(nextRetryContext, duration);\n\t\t\tfuture.completeExceptionally(t);\n\t\t}\n\t}\n\n\tprotected void logFailure(AsyncRetryContext nextRetryContext, long duration) {\n\t\tlog.trace(\"Giving up after {} retries, last run took: {}ms, last exception: \",\n\t\t\t\tcontext.getRetryCount(),\n\t\t\t\tduration,\n\t\t\t\tnextRetryContext.getLastThrowable());\n\t}\n\n\tprivate long calculateNextDelay(long taskDurationMillis, AsyncRetryContext nextRetryContext, Backoff backoff) {\n\t\tfinal long delay = backoff.delayMillis(nextRetryContext);\n\t\treturn delay - (parent.isFixedDelay()? taskDurationMillis : 0);\n\t}\n\n\tprivate void retryWithDelay(AsyncRetryContext nextRetryContext, long delay, long duration) {\n\t\tfinal RetryJob<V> nextTask = nextTask(nextRetryContext);\n\t\tparent.getScheduler().schedule(nextTask, delay, MILLISECONDS);\n\t\tlogRetry(nextRetryContext, delay, duration);\n\t}\n\n\tprotected void logRetry(AsyncRetryContext nextRetryContext, long delay, long duration) {\n\t\tfinal Date nextRunDate = new Date(System.currentTimeMillis() + delay);\n\t\tlog.trace(\"Retry {} failed after {}ms, scheduled next retry in {}ms ({})\",\n\t\t\t\tcontext.getRetryCount(),\n\t\t\t\tduration,\n\t\t\t\tdelay,\n\t\t\t\tnextRunDate,\n\t\t\t\tnextRetryContext.getLastThrowable());\n\t}\n\n\t@Override\n\tpublic void run() {\n\t\trun(System.currentTimeMillis());\n\t}\n\n\tprotected abstract void run(long startTime);\n\n\tprotected abstract RetryJob<V> nextTask(AsyncRetryContext nextRetryContext);\n\n\tprotected void complete(V result, long duration) {\n\t\tlogSuccess(context, result, duration);\n\t\tfuture.complete(result);\n\t}\n\n\tpublic CompletableFuture<V> getFuture() {\n\t\treturn future;\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/nurkiewicz/asyncretry/SyncRetryExecutor.java",
    "content": "package com.nurkiewicz.asyncretry;\n\nimport com.nurkiewicz.asyncretry.function.RetryCallable;\nimport com.nurkiewicz.asyncretry.function.RetryRunnable;\nimport com.nurkiewicz.asyncretry.policy.RetryPolicy;\n\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.CompletableFuture;\n\n/**\n * Singleton instance of {@link RetryExecutor} that executes tasks in the same thread and without retry.\n * Useful for testing or when no-op implementation of {@link RetryExecutor} is needed.\n * This implementation, while implements the API, runs all tasks synchronously so that returned futures are already completed.\n * Exceptions are not thrown but wrapped in Future as well.\n * @since 0.0.6\n */\npublic enum SyncRetryExecutor implements RetryExecutor {\n\n\tINSTANCE;\n\n\tprivate static final AsyncRetryContext RETRY_CONTEXT = new AsyncRetryContext(RetryPolicy.DEFAULT);\n\n\t@Override\n\tpublic CompletableFuture<Void> doWithRetry(RetryRunnable action) {\n\t\ttry {\n\t\t\taction.run(RETRY_CONTEXT);\n\t\t\treturn CompletableFuture.completedFuture(null);\n\t\t} catch (Exception e) {\n\t\t\treturn failedFuture(e);\n\t\t}\n\t}\n\n\t@Override\n\tpublic <V> CompletableFuture<V> getWithRetry(Callable<V> task) {\n\t\ttry {\n\t\t\treturn CompletableFuture.completedFuture(task.call());\n\t\t} catch (Exception e) {\n\t\t\treturn failedFuture(e);\n\t\t}\n\t}\n\n\t@Override\n\tpublic <V> CompletableFuture<V> getWithRetry(RetryCallable<V> task) {\n\t\ttry {\n\t\t\treturn CompletableFuture.completedFuture(task.call(RETRY_CONTEXT));\n\t\t} catch (Exception e) {\n\t\t\treturn failedFuture(e);\n\t\t}\n\t}\n\n\t@Override\n\tpublic <V> CompletableFuture<V> getFutureWithRetry(RetryCallable<CompletableFuture<V>> task) {\n\t\ttry {\n\t\t\treturn task.call(RETRY_CONTEXT);\n\t\t} catch (Exception e) {\n\t\t\treturn failedFuture(e);\n\t\t}\n\t}\n\n\tprivate static <T> CompletableFuture<T> failedFuture(Exception e) {\n\t\tfinal CompletableFuture<T> promise = new CompletableFuture<>();\n\t\tpromise.completeExceptionally(e);\n\t\treturn promise;\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/nurkiewicz/asyncretry/SyncRetryJob.java",
    "content": "package com.nurkiewicz.asyncretry;\n\nimport com.nurkiewicz.asyncretry.function.RetryCallable;\n\nimport java.util.concurrent.CompletableFuture;\n\nclass SyncRetryJob<V> extends RetryJob<V> {\n\n\tprivate final RetryCallable<V> userTask;\n\n\tpublic SyncRetryJob(RetryCallable<V> userTask, AsyncRetryExecutor parent) {\n\t\tthis(userTask, parent, new AsyncRetryContext(parent.getRetryPolicy()), new CompletableFuture<>());\n\t}\n\n\tpublic SyncRetryJob(RetryCallable<V> userTask, AsyncRetryExecutor parent, AsyncRetryContext context, CompletableFuture<V> future) {\n\t\tsuper(context, parent, future);\n\t\tthis.userTask = userTask;\n\t}\n\n\t@Override\n\tpublic void run(long startTime) {\n\t\ttry {\n\t\t\tfinal V result = userTask.call(context);\n\t\t\tcomplete(result, System.currentTimeMillis() - startTime);\n\t\t} catch (Throwable t) {\n\t\t\thandleThrowable(t, System.currentTimeMillis() - startTime);\n\t\t}\n\t}\n\n\tprotected RetryJob<V> nextTask(AsyncRetryContext nextRetryContext) {\n\t\treturn new SyncRetryJob<>(userTask, parent, nextRetryContext, future);\n\t}\n\n}"
  },
  {
    "path": "src/main/java/com/nurkiewicz/asyncretry/backoff/Backoff.java",
    "content": "package com.nurkiewicz.asyncretry.backoff;\n\nimport com.nurkiewicz.asyncretry.RetryContext;\n\n/**\n * @author Tomasz Nurkiewicz\n * @since 7/17/13, 11:15 PM\n */\npublic interface Backoff {\n\n\tBackoff DEFAULT = new FixedIntervalBackoff();\n\n\tlong delayMillis(RetryContext context);\n\n\tdefault Backoff withUniformJitter() {\n\t\treturn new UniformRandomBackoff(this);\n\t}\n\n\tdefault Backoff withUniformJitter(long range) {\n\t\treturn new UniformRandomBackoff(this, range);\n\t}\n\n\tdefault Backoff withProportionalJitter() {\n\t\treturn new ProportionalRandomBackoff(this);\n\t}\n\n\tdefault Backoff withProportionalJitter(double multiplier) {\n\t\treturn new ProportionalRandomBackoff(this, multiplier);\n\t}\n\n\tdefault Backoff withMinDelay(long minDelayMillis) {\n\t\treturn new BoundedMinBackoff(this, minDelayMillis);\n\t}\n\n\tdefault Backoff withMinDelay() {\n\t\treturn new BoundedMinBackoff(this);\n\t}\n\n\tdefault Backoff withMaxDelay(long maxDelayMillis) {\n\t\treturn new BoundedMaxBackoff(this, maxDelayMillis);\n\t}\n\n\tdefault Backoff withMaxDelay() {\n\t\treturn new BoundedMaxBackoff(this);\n\t}\n\n\tdefault Backoff withFirstRetryNoDelay() {\n\t\treturn new FirstRetryNoDelayBackoff(this);\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/nurkiewicz/asyncretry/backoff/BackoffWrapper.java",
    "content": "package com.nurkiewicz.asyncretry.backoff;\n\nimport java.util.Objects;\n\n/**\n * @author Tomasz Nurkiewicz\n * @since 7/17/13, 11:16 PM\n */\npublic abstract class BackoffWrapper implements Backoff {\n\n\tprotected final Backoff target;\n\n\tpublic BackoffWrapper(Backoff target) {\n\t\tthis.target = Objects.requireNonNull(target);\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/nurkiewicz/asyncretry/backoff/BoundedMaxBackoff.java",
    "content": "package com.nurkiewicz.asyncretry.backoff;\n\nimport com.nurkiewicz.asyncretry.RetryContext;\n\n/**\n * @author Tomasz Nurkiewicz\n * @since 7/16/13, 7:04 PM\n */\npublic class BoundedMaxBackoff extends BackoffWrapper {\n\n\tpublic static final long DEFAULT_MAX_DELAY_MILLIS = 10_000;\n\n\tprivate final long maxDelayMillis;\n\n\tpublic BoundedMaxBackoff(Backoff target) {\n\t\tthis(target, DEFAULT_MAX_DELAY_MILLIS);\n\t}\n\n\tpublic BoundedMaxBackoff(Backoff target, long maxDelayMillis) {\n\t\tsuper(target);\n\t\tthis.maxDelayMillis = maxDelayMillis;\n\t}\n\n\t@Override\n\tpublic long delayMillis(RetryContext context) {\n\t\treturn Math.min(target.delayMillis(context), maxDelayMillis);\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/nurkiewicz/asyncretry/backoff/BoundedMinBackoff.java",
    "content": "package com.nurkiewicz.asyncretry.backoff;\n\nimport com.nurkiewicz.asyncretry.RetryContext;\n\n/**\n * @author Tomasz Nurkiewicz\n * @since 7/16/13, 7:04 PM\n */\npublic class BoundedMinBackoff extends BackoffWrapper {\n\n\tpublic static final long DEFAULT_MIN_DELAY_MILLIS = 100;\n\n\tprivate final long minDelayMillis;\n\n\tpublic BoundedMinBackoff(Backoff target) {\n\t\tthis(target, DEFAULT_MIN_DELAY_MILLIS);\n\t}\n\n\tpublic BoundedMinBackoff(Backoff target, long minDelayMillis) {\n\t\tsuper(target);\n\t\tthis.minDelayMillis = minDelayMillis;\n\t}\n\n\t@Override\n\tpublic long delayMillis(RetryContext context) {\n\t\treturn Math.max(target.delayMillis(context), minDelayMillis);\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/nurkiewicz/asyncretry/backoff/ExponentialDelayBackoff.java",
    "content": "package com.nurkiewicz.asyncretry.backoff;\n\nimport com.nurkiewicz.asyncretry.RetryContext;\n\n/**\n * @author Tomasz Nurkiewicz\n * @since 7/16/13, 6:17 PM\n */\npublic class ExponentialDelayBackoff implements Backoff {\n\n\tprivate final long initialDelayMillis;\n\tprivate final double multiplier;\n\n\tpublic ExponentialDelayBackoff(long initialDelayMillis, double multiplier) {\n\t\tif (initialDelayMillis <= 0) {\n\t\t\tthrow new IllegalArgumentException(\"Initial delay must be positive but was: \" + initialDelayMillis);\n\t\t}\n\t\tthis.initialDelayMillis = initialDelayMillis;\n\t\tthis.multiplier = multiplier;\n\t}\n\n\t@Override\n\tpublic long delayMillis(RetryContext context) {\n\t\treturn (long) (initialDelayMillis * Math.pow(multiplier, context.getRetryCount() - 1));\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/nurkiewicz/asyncretry/backoff/FirstRetryNoDelayBackoff.java",
    "content": "package com.nurkiewicz.asyncretry.backoff;\n\nimport com.nurkiewicz.asyncretry.AsyncRetryContext;\nimport com.nurkiewicz.asyncretry.RetryContext;\n\npublic class FirstRetryNoDelayBackoff extends BackoffWrapper {\n\n\tpublic FirstRetryNoDelayBackoff(Backoff target) {\n\t\tsuper(target);\n\t}\n\n\t@Override\n\tpublic long delayMillis(RetryContext context) {\n\t\tif (context.isFirstRetry()) {\n\t\t\treturn 0;\n\t\t} else {\n\t\t\treturn target.delayMillis(decrementRetryCount(context));\n\t\t}\n\t}\n\n\tprivate RetryContext decrementRetryCount(RetryContext context) {\n\t\treturn ((AsyncRetryContext) context).prevRetry();\n\t}\n\n}\n"
  },
  {
    "path": "src/main/java/com/nurkiewicz/asyncretry/backoff/FixedIntervalBackoff.java",
    "content": "package com.nurkiewicz.asyncretry.backoff;\n\nimport com.nurkiewicz.asyncretry.RetryContext;\n\n/**\n * @author Tomasz Nurkiewicz\n * @since 7/16/13, 6:14 PM\n */\npublic class FixedIntervalBackoff implements Backoff {\n\n\tpublic static final long DEFAULT_PERIOD_MILLIS = 1000;\n\n\tprivate final long intervalMillis;\n\n\tpublic FixedIntervalBackoff() {\n\t\tthis(DEFAULT_PERIOD_MILLIS);\n\t}\n\n\tpublic FixedIntervalBackoff(long intervalMillis) {\n\t\tthis.intervalMillis = intervalMillis;\n\t}\n\n\t@Override\n\tpublic long delayMillis(RetryContext context) {\n\t\treturn intervalMillis;\n\t}\n\n}\n"
  },
  {
    "path": "src/main/java/com/nurkiewicz/asyncretry/backoff/ProportionalRandomBackoff.java",
    "content": "package com.nurkiewicz.asyncretry.backoff;\n\nimport java.util.Random;\n\n/**\n * @author Tomasz Nurkiewicz\n * @since 7/16/13, 7:49 PM\n */\npublic class ProportionalRandomBackoff extends RandomBackoff {\n\n\t/**\n\t * Randomly up to +/- 10%\n\t */\n\tpublic static final double DEFAULT_MULTIPLIER = 0.1;\n\n\tprivate final double multiplier;\n\n\tpublic ProportionalRandomBackoff(Backoff target) {\n\t\tthis(target, DEFAULT_MULTIPLIER);\n\t}\n\n\tpublic ProportionalRandomBackoff(Backoff target, Random random) {\n\t\tthis(target, DEFAULT_MULTIPLIER, random);\n\t}\n\n\tpublic ProportionalRandomBackoff(Backoff target, double multiplier) {\n\t\tsuper(target);\n\t\tthis.multiplier = multiplier;\n\t}\n\n\tpublic ProportionalRandomBackoff(Backoff target, double multiplier, Random random) {\n\t\tsuper(target, random);\n\t\tthis.multiplier = multiplier;\n\t}\n\n\t@Override\n\tlong addRandomJitter(long initialDelay) {\n\t\tfinal double randomMultiplier = (1 - 2 * random().nextDouble()) * multiplier;\n\t\treturn (long) (initialDelay * (1 + randomMultiplier));\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/nurkiewicz/asyncretry/backoff/RandomBackoff.java",
    "content": "package com.nurkiewicz.asyncretry.backoff;\n\nimport com.nurkiewicz.asyncretry.RetryContext;\n\nimport java.util.Random;\nimport java.util.concurrent.ThreadLocalRandom;\nimport java.util.function.Supplier;\n\n/**\n * @author Tomasz Nurkiewicz\n * @since 7/16/13, 7:30 PM\n */\nabstract public class RandomBackoff extends BackoffWrapper {\n\n\tprivate final Supplier<Random> randomSource;\n\n\tprotected RandomBackoff(Backoff target) {\n\t\tthis(target, ThreadLocalRandom::current);\n\t}\n\n\tprotected RandomBackoff(Backoff target, Random randomSource) {\n\t\tthis(target, () -> randomSource);\n\t}\n\n\tprivate RandomBackoff(Backoff target, Supplier<Random> randomSource) {\n\t\tsuper(target);\n\t\tthis.randomSource = randomSource;\n\t}\n\n\t@Override\n\tpublic long delayMillis(RetryContext context) {\n\t\tfinal long initialDelay = target.delayMillis(context);\n\t\tfinal long randomDelay = addRandomJitter(initialDelay);\n\t\treturn Math.max(randomDelay, 0);\n\t}\n\n\tabstract long addRandomJitter(long initialDelay);\n\n\tprotected Random random() {\n\t\treturn randomSource.get();\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/nurkiewicz/asyncretry/backoff/UniformRandomBackoff.java",
    "content": "package com.nurkiewicz.asyncretry.backoff;\n\nimport java.util.Random;\n\n/**\n * @author Tomasz Nurkiewicz\n * @since 7/16/13, 7:26 PM\n */\npublic class UniformRandomBackoff extends RandomBackoff {\n\n\t/**\n\t * Randomly between +/- 100ms\n\t */\n\tpublic static final long DEFAULT_RANDOM_RANGE_MILLIS = 100;\n\n\tprivate final long range;\n\n\tpublic UniformRandomBackoff(Backoff target) {\n\t\tthis(target, DEFAULT_RANDOM_RANGE_MILLIS);\n\t}\n\n\tpublic UniformRandomBackoff(Backoff target, Random random) {\n\t\tthis(target, DEFAULT_RANDOM_RANGE_MILLIS, random);\n\t}\n\n\tpublic UniformRandomBackoff(Backoff target, final long range) {\n\t\tsuper(target);\n\t\tthis.range = range;\n\t}\n\n\tpublic UniformRandomBackoff(Backoff target, final long range, Random random) {\n\t\tsuper(target, random);\n\t\tthis.range = range;\n\t}\n\n\t@Override\n\tlong addRandomJitter(long initialDelay) {\n\t\tfinal double uniformRandom = (1 - random().nextDouble() * 2) * range;\n\t\treturn (long) (initialDelay + uniformRandom);\n\t}\n\n\n}\n"
  },
  {
    "path": "src/main/java/com/nurkiewicz/asyncretry/function/RetryCallable.java",
    "content": "package com.nurkiewicz.asyncretry.function;\n\nimport com.nurkiewicz.asyncretry.RetryContext;\n\n/**\n * @author Tomasz Nurkiewicz\n * @since 7/20/13, 9:34 PM\n */\n@FunctionalInterface\npublic interface RetryCallable<V> {\n\n\tV call(RetryContext context) throws Exception;\n\n}\n"
  },
  {
    "path": "src/main/java/com/nurkiewicz/asyncretry/function/RetryRunnable.java",
    "content": "package com.nurkiewicz.asyncretry.function;\n\nimport com.nurkiewicz.asyncretry.RetryContext;\n\n/**\n * @author Tomasz Nurkiewicz\n * @since 7/20/13, 9:36 PM\n */\n@FunctionalInterface\npublic interface RetryRunnable {\n\n\tvoid run(RetryContext context) throws Exception;\n\n}\n"
  },
  {
    "path": "src/main/java/com/nurkiewicz/asyncretry/policy/AbortRetryException.java",
    "content": "package com.nurkiewicz.asyncretry.policy;\n\n/**\n * @author Tomasz Nurkiewicz\n * @since 7/16/13, 10:23 PM\n */\npublic class AbortRetryException extends RuntimeException {\n\n\tpublic AbortRetryException() {\n\t}\n\n}\n"
  },
  {
    "path": "src/main/java/com/nurkiewicz/asyncretry/policy/RetryPolicy.java",
    "content": "package com.nurkiewicz.asyncretry.policy;\n\nimport com.nurkiewicz.asyncretry.RetryContext;\n\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.Set;\nimport java.util.function.Predicate;\n\n/**\n * @author Tomasz Nurkiewicz\n * @since 7/16/13, 6:05 PM\n */\npublic class RetryPolicy {\n\n\tpublic static final RetryPolicy DEFAULT = new RetryPolicy();\n\n\tprivate final int maxRetries;\n\tprivate final Set<Class<? extends Throwable>> retryOn;\n\tprivate final Set<Class<? extends Throwable>> abortOn;\n\tprivate final Predicate<Throwable> retryPredicate;\n\tprivate final Predicate<Throwable> abortPredicate;\n\n\tpublic RetryPolicy retryOn(Class<? extends Throwable>... retryOnThrowables) {\n\t\treturn new RetryPolicy(maxRetries, setPlusElems(retryOn, retryOnThrowables), abortOn, retryPredicate, abortPredicate);\n\t}\n\n\tpublic RetryPolicy abortOn(Class<? extends Throwable>... abortOnThrowables) {\n\t\treturn new RetryPolicy(maxRetries, retryOn, setPlusElems(abortOn, abortOnThrowables), retryPredicate, abortPredicate);\n\t}\n\n\tpublic RetryPolicy abortIf(Predicate<Throwable> abortPredicate) {\n\t\treturn new RetryPolicy(maxRetries, retryOn, abortOn, retryPredicate, this.abortPredicate.or(abortPredicate));\n\t}\n\n\tpublic RetryPolicy retryIf(Predicate<Throwable> retryPredicate) {\n\t\treturn new RetryPolicy(maxRetries, retryOn, abortOn, this.retryPredicate.or(retryPredicate), abortPredicate);\n\t}\n\n\tpublic RetryPolicy dontRetry() {\n\t\treturn new RetryPolicy(0, retryOn, abortOn, retryPredicate, abortPredicate);\n\t}\n\n\tpublic RetryPolicy withMaxRetries(int times) {\n\t\treturn new RetryPolicy(times, retryOn, abortOn, retryPredicate, abortPredicate);\n\t}\n\n\tpublic RetryPolicy(int maxRetries, Set<Class<? extends Throwable>> retryOn, Set<Class<? extends Throwable>> abortOn, Predicate<Throwable> retryPredicate, Predicate<Throwable> abortPredicate) {\n\t\tthis.maxRetries = maxRetries;\n\t\tthis.retryOn = retryOn;\n\t\tthis.abortOn = abortOn;\n\t\tthis.retryPredicate = retryPredicate;\n\t\tthis.abortPredicate = abortPredicate;\n\t}\n\n\tpublic RetryPolicy() {\n\t\tthis(Integer.MAX_VALUE, Collections.emptySet(), Collections.emptySet(), th -> false, th -> false);\n\t}\n\n\tpublic boolean shouldContinue(RetryContext context) {\n\t\tif (tooManyRetries(context)) {\n\t\t\treturn false;\n\t\t}\n\t\tif (abortPredicate.test(context.getLastThrowable())) {\n\t\t\treturn false;\n\t\t}\n\t\tif (retryPredicate.test(context.getLastThrowable())) {\n\t\t\treturn true;\n\t\t}\n\t\treturn exceptionClassRetryable(context);\n\t}\n\n\tprivate boolean tooManyRetries(RetryContext context) {\n\t\treturn context.getRetryCount() > maxRetries;\n\t}\n\n\tprivate boolean exceptionClassRetryable(RetryContext context) {\n\t\tif (context.getLastThrowable() == null) {\n\t\t\treturn false;\n\t\t}\n\t\tfinal Class<? extends Throwable> e = context.getLastThrowable().getClass();\n\t\tif (abortOn.isEmpty()) {\n\t\t\treturn matches(e, retryOn);\n\t\t} else {\n\t\t\treturn !matches(e, abortOn) && matches(e, retryOn);\n\t\t}\n\t}\n\n\tprivate static boolean matches(Class<? extends Throwable> throwable, Set<Class<? extends Throwable>> set) {\n\t\treturn set.isEmpty() || set.stream().anyMatch(c -> c.isAssignableFrom(throwable));\n\t}\n\n\tprivate static <T> Set<T> setPlusElems(Set<T> initial, T... newElement) {\n\t\tfinal HashSet<T> copy = new HashSet<>(initial);\n\t\tcopy.addAll(Arrays.asList(newElement));\n\t\treturn Collections.unmodifiableSet(copy);\n\t}\n\n}\n"
  },
  {
    "path": "src/test/java/com/nurkiewicz/asyncretry/AbstractBaseTestCase.java",
    "content": "package com.nurkiewicz.asyncretry;\n\nimport com.nurkiewicz.asyncretry.policy.RetryPolicy;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.testng.annotations.BeforeMethod;\n\nimport java.util.concurrent.ScheduledExecutorService;\nimport java.util.concurrent.TimeUnit;\n\nimport static org.mockito.BDDMockito.given;\nimport static org.mockito.Matchers.anyLong;\nimport static org.mockito.Matchers.eq;\nimport static org.mockito.Matchers.notNull;\n\n/**\n * @author Tomasz Nurkiewicz\n * @since 5/10/13, 9:56 PM\n */\npublic class AbstractBaseTestCase {\n\n\tpublic static final String DON_T_PANIC = \"Don't panic!\";\n\n\t@Mock\n\tprotected ScheduledExecutorService schedulerMock;\n\n\t@Mock\n\tprotected FaultyService serviceMock;\n\n\t@BeforeMethod(alwaysRun=true)\n\tpublic void injectMocks() {\n\t\tMockitoAnnotations.initMocks(this);\n\t\tsetupMocks();\n\t}\n\n\tprivate void setupMocks() {\n\t\tgiven(schedulerMock.schedule(notNullRunnable(), anyLong(), eq(TimeUnit.MILLISECONDS))).willAnswer(invocation -> {\n\t\t\t((Runnable) invocation.getArguments()[0]).run();\n\t\t\treturn null;\n\t\t});\n\t}\n\n\tprotected Runnable notNullRunnable() {\n\t\treturn (Runnable) notNull();\n\t}\n\n\tprotected RetryContext notNullRetryContext() {\n\t\treturn (RetryContext) notNull();\n\t}\n\n\tprotected TimeUnit millis() {\n\t\treturn eq(TimeUnit.MILLISECONDS);\n\t}\n\n\tprotected RetryContext anyRetry() {\n\t\treturn retry(1);\n\t}\n\n\tprotected RetryContext retry(int ret) {\n\t\treturn new AsyncRetryContext(RetryPolicy.DEFAULT, ret, new Exception());\n\t}\n\n}\n"
  },
  {
    "path": "src/test/java/com/nurkiewicz/asyncretry/AsyncRetryContextTest.java",
    "content": "package com.nurkiewicz.asyncretry;\n\nimport org.mockito.InOrder;\nimport org.testng.annotations.Test;\n\nimport static org.mockito.Matchers.anyBoolean;\nimport static org.mockito.Mockito.doThrow;\nimport static org.mockito.Mockito.inOrder;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\n\n/**\n * @author Tomasz Nurkiewicz\n * @since 7/17/13, 9:34 PM\n */\npublic class AsyncRetryContextTest extends AbstractBaseTestCase {\n\n\t@Test\n\tpublic void shouldNotRetryIfRetriesForbidden() throws Exception {\n\t\t//given\n\t\tfinal RetryExecutor executor = new AsyncRetryExecutor(schedulerMock).dontRetry();\n\n\t\t//when\n\t\texecutor.doWithRetry(ctx -> serviceMock.withFlag(ctx.willRetry()));\n\n\t\t//then\n\t\tverify(serviceMock).withFlag(false);\n\t}\n\n\t@Test\n\tpublic void shouldSayItWillRetryIfUnlimitedNumberOfRetries() throws Exception {\n\t\t//given\n\t\tfinal RetryExecutor executor = new AsyncRetryExecutor(schedulerMock);\n\n\t\t//when\n\t\texecutor.doWithRetry(ctx -> serviceMock.withFlag(ctx.willRetry()));\n\n\t\t//then\n\t\tverify(serviceMock).withFlag(true);\n\t}\n\n\t@Test\n\tpublic void shouldSayItWillRetryOnFirstFewCases() throws Exception {\n\t\t//given\n\t\tfinal RetryExecutor executor = new AsyncRetryExecutor(schedulerMock).withMaxRetries(2);\n\t\tdoThrow(IllegalStateException.class).when(serviceMock).withFlag(anyBoolean());\n\n\t\t//when\n\t\texecutor.doWithRetry(ctx -> serviceMock.withFlag(ctx.willRetry()));\n\n\t\t//then\n\t\tfinal InOrder order = inOrder(serviceMock);\n\t\torder.verify(serviceMock, times(2)).withFlag(true);\n\t\torder.verify(serviceMock).withFlag(false);\n\t\torder.verifyNoMoreInteractions();\n\t}\n\n}\n"
  },
  {
    "path": "src/test/java/com/nurkiewicz/asyncretry/AsyncRetryExecutorHappyTest.java",
    "content": "package com.nurkiewicz.asyncretry;\n\nimport org.testng.annotations.Test;\n\nimport java.util.concurrent.CompletableFuture;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.BDDMockito.given;\nimport static org.mockito.Matchers.eq;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.verifyNoMoreInteractions;\n\n/**\n * @author Tomasz Nurkiewicz\n * @since 7/16/13, 10:51 PM\n */\npublic class AsyncRetryExecutorHappyTest extends AbstractBaseTestCase {\n\n\t@Test\n\tpublic void shouldNotRetryIfCompletesAfterFirstExecution() throws Exception {\n\t\t//given\n\t\tfinal RetryExecutor executor = new AsyncRetryExecutor(schedulerMock);\n\n\t\t//when\n\t\texecutor.doWithRetry(ctx -> serviceMock.alwaysSucceeds());\n\n\t\t//then\n\t\tverify(schedulerMock).schedule(notNullRunnable(), eq(0L), millis());\n\t\tverifyNoMoreInteractions(schedulerMock);\n\t}\n\n\t@Test\n\tpublic void shouldCallUserTaskOnlyOnceIfItDoesntFail() throws Exception {\n\t\t//given\n\t\tfinal RetryExecutor executor = new AsyncRetryExecutor(schedulerMock);\n\n\t\t//when\n\t\texecutor.doWithRetry(ctx -> serviceMock.alwaysSucceeds());\n\n\t\t//then\n\t\tverify(serviceMock).alwaysSucceeds();\n\t}\n\n\t@Test\n\tpublic void shouldReturnResultOfFirstSuccessfulCall() throws Exception {\n\t\t//given\n\t\tfinal RetryExecutor executor = new AsyncRetryExecutor(schedulerMock);\n\t\tgiven(serviceMock.alwaysSucceeds()).willReturn(42);\n\n\t\t//when\n\t\tfinal CompletableFuture<Integer> future = executor.getWithRetry(serviceMock::alwaysSucceeds);\n\n\t\t//then\n\t\tassertThat(future.get()).isEqualTo(42);\n\t}\n\n\t@Test\n\tpublic void shouldReturnEvenIfNoRetryPolicy() throws Exception {\n\t\t//given\n\t\tfinal RetryExecutor executor = new AsyncRetryExecutor(schedulerMock).dontRetry();\n\t\tgiven(serviceMock.alwaysSucceeds()).willReturn(42);\n\n\t\t//when\n\t\tfinal CompletableFuture<Integer> future = executor.getWithRetry(serviceMock::alwaysSucceeds);\n\n\t\t//then\n\t\tassertThat(future.get()).isEqualTo(42);\n\t}\n\n}\n"
  },
  {
    "path": "src/test/java/com/nurkiewicz/asyncretry/AsyncRetryExecutorManualAbortTest.java",
    "content": "package com.nurkiewicz.asyncretry;\n\nimport org.assertj.core.api.Assertions;\nimport org.mockito.InOrder;\nimport org.testng.annotations.Test;\n\nimport java.math.BigDecimal;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.ExecutionException;\n\nimport static com.nurkiewicz.asyncretry.backoff.FixedIntervalBackoff.DEFAULT_PERIOD_MILLIS;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.BDDMockito.given;\nimport static org.mockito.Mockito.eq;\nimport static org.mockito.Mockito.inOrder;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\n\n/**\n * @author Tomasz Nurkiewicz\n * @since 7/16/13, 10:51 PM\n */\npublic class AsyncRetryExecutorManualAbortTest extends AbstractBaseTestCase {\n\n\t@Test\n\tpublic void shouldRethrowIfFirstExecutionThrowsAnExceptionAndNoRetry() throws Exception {\n\t\t//given\n\t\tfinal RetryExecutor executor = new AsyncRetryExecutor(schedulerMock).dontRetry();\n\t\tgiven(serviceMock.sometimesFails()).\n\t\t\t\twillThrow(new IllegalStateException(DON_T_PANIC));\n\n\t\t//when\n\t\tfinal CompletableFuture<String> future = executor.getWithRetry(serviceMock::sometimesFails);\n\n\t\t//then\n\t\tassertThat(future.isCompletedExceptionally()).isTrue();\n\t\ttry {\n\t\t\tfuture.get();\n\t\t\tAssertions.failBecauseExceptionWasNotThrown(IllegalStateException.class);\n\t\t} catch (ExecutionException e) {\n\t\t\tfinal Throwable actualCause = e.getCause();\n\t\t\tassertThat(actualCause).isInstanceOf(IllegalStateException.class);\n\t\t\tassertThat(actualCause.getMessage()).isEqualToIgnoringCase(DON_T_PANIC);\n\t\t}\n\t}\n\n\t@Test\n\tpublic void shouldRetryAfterOneExceptionAndReturnValue() throws Exception {\n\t\t//given\n\t\tfinal RetryExecutor executor = new AsyncRetryExecutor(schedulerMock);\n\t\tgiven(serviceMock.sometimesFails()).\n\t\t\t\twillThrow(IllegalStateException.class).\n\t\t\t\twillReturn(\"Foo\");\n\n\t\t//when\n\t\tfinal CompletableFuture<String> future = executor.getWithRetry(serviceMock::sometimesFails);\n\n\t\t//then\n\t\tassertThat(future.get()).isEqualTo(\"Foo\");\n\t}\n\n\t@Test\n\tpublic void shouldSucceedWhenOnlyOneRetryAllowed() throws Exception {\n\t\t//given\n\t\tfinal RetryExecutor executor = new AsyncRetryExecutor(schedulerMock).withMaxRetries(1);\n\t\tgiven(serviceMock.sometimesFails()).\n\t\t\t\twillThrow(IllegalStateException.class).\n\t\t\t\twillReturn(\"Foo\");\n\n\t\t//when\n\t\tfinal CompletableFuture<String> future = executor.getWithRetry(serviceMock::sometimesFails);\n\n\t\t//then\n\t\tassertThat(future.get()).isEqualTo(\"Foo\");\n\t}\n\n\t@Test\n\tpublic void shouldRetryOnceIfFirstExecutionThrowsException() throws Exception {\n\t\t//given\n\t\tfinal RetryExecutor executor = new AsyncRetryExecutor(schedulerMock);\n\t\tgiven(serviceMock.sometimesFails()).\n\t\t\t\twillThrow(IllegalStateException.class).\n\t\t\t\twillReturn(\"Foo\");\n\n\t\t//when\n\t\texecutor.getWithRetry(serviceMock::sometimesFails);\n\n\t\t//then\n\t\tverify(serviceMock, times(2)).sometimesFails();\n\t}\n\n\t@Test\n\tpublic void shouldScheduleRetryWithDefaultDelay() throws Exception {\n\t\t//given\n\t\tfinal RetryExecutor executor = new AsyncRetryExecutor(schedulerMock);\n\t\tgiven(serviceMock.sometimesFails()).\n\t\t\t\twillThrow(IllegalStateException.class).\n\t\t\t\twillReturn(\"Foo\");\n\n\t\t//when\n\t\texecutor.getWithRetry(serviceMock::sometimesFails);\n\n\t\t//then\n\t\tfinal InOrder inOrder = inOrder(schedulerMock);\n\t\tinOrder.verify(schedulerMock).schedule(notNullRunnable(), eq(0L), millis());\n\t\tinOrder.verify(schedulerMock).schedule(notNullRunnable(), eq(DEFAULT_PERIOD_MILLIS), millis());\n\t\tinOrder.verifyNoMoreInteractions();\n\t}\n\n\t@Test\n\tpublic void shouldPassCorrectRetryCountToEachInvocationInContext() throws Exception {\n\t\t//given\n\t\tfinal RetryExecutor executor = new AsyncRetryExecutor(schedulerMock);\n\t\tgiven(serviceMock.calculateSum(0)).willThrow(IllegalStateException.class);\n\t\tgiven(serviceMock.calculateSum(1)).willReturn(BigDecimal.ONE);\n\n\t\t//when\n\t\texecutor.getWithRetry(ctx -> serviceMock.calculateSum(ctx.getRetryCount()));\n\n\t\t//then\n\t\tfinal InOrder order = inOrder(serviceMock);\n\t\torder.verify(serviceMock).calculateSum(0);\n\t\torder.verify(serviceMock).calculateSum(1);\n\t\torder.verifyNoMoreInteractions();\n\t}\n\n}\n"
  },
  {
    "path": "src/test/java/com/nurkiewicz/asyncretry/AsyncRetryExecutorManyFailuresTest.java",
    "content": "package com.nurkiewicz.asyncretry;\n\nimport org.assertj.core.api.Assertions;\nimport org.mockito.InOrder;\nimport org.testng.annotations.Test;\n\nimport java.math.BigDecimal;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.ExecutionException;\n\nimport static com.nurkiewicz.asyncretry.backoff.FixedIntervalBackoff.DEFAULT_PERIOD_MILLIS;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.BDDMockito.given;\nimport static org.mockito.Mockito.eq;\nimport static org.mockito.Mockito.inOrder;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\n\n/**\n * @author Tomasz Nurkiewicz\n * @since 7/16/13, 10:51 PM\n */\npublic class AsyncRetryExecutorManyFailuresTest extends AbstractBaseTestCase {\n\n\t@Test\n\tpublic void shouldRethrowIfFirstFewExecutionsThrow() throws Exception {\n\t\t//given\n\t\tfinal RetryExecutor executor = new AsyncRetryExecutor(schedulerMock).withMaxRetries(2);\n\t\tgiven(serviceMock.sometimesFails()).willThrow(new IllegalStateException(DON_T_PANIC));\n\n\t\t//when\n\t\tfinal CompletableFuture<String> future = executor.getWithRetry(serviceMock::sometimesFails);\n\n\t\t//then\n\t\tassertThat(future.isCompletedExceptionally()).isTrue();\n\t\ttry {\n\t\t\tfuture.get();\n\t\t\tAssertions.failBecauseExceptionWasNotThrown(IllegalStateException.class);\n\t\t} catch (ExecutionException e) {\n\t\t\tfinal Throwable actualCause = e.getCause();\n\t\t\tassertThat(actualCause).isInstanceOf(IllegalStateException.class);\n\t\t\tassertThat(actualCause.getMessage()).isEqualToIgnoringCase(DON_T_PANIC);\n\t\t}\n\t}\n\n\t@Test\n\tpublic void shouldRetryAfterManyExceptionsAndReturnValue() throws Exception {\n\t\t//given\n\t\tfinal RetryExecutor executor = new AsyncRetryExecutor(schedulerMock);\n\t\tgiven(serviceMock.sometimesFails()).\n\t\t\t\twillThrow(IllegalStateException.class, IllegalStateException.class, IllegalStateException.class).\n\t\t\t\twillReturn(\"Foo\");\n\n\t\t//when\n\t\tfinal CompletableFuture<String> future = executor.getWithRetry(serviceMock::sometimesFails);\n\n\t\t//then\n\t\tassertThat(future.get()).isEqualTo(\"Foo\");\n\t}\n\n\t@Test\n\tpublic void shouldSucceedWhenTheSameNumberOfRetriesAsFailuresAllowed() throws Exception {\n\t\t//given\n\t\tfinal RetryExecutor executor = new AsyncRetryExecutor(schedulerMock).withMaxRetries(3);\n\t\tgiven(serviceMock.sometimesFails()).\n\t\t\t\twillThrow(IllegalStateException.class, IllegalStateException.class, IllegalStateException.class).\n\t\t\t\twillReturn(\"Foo\");\n\n\t\t//when\n\t\tfinal CompletableFuture<String> future = executor.getWithRetry(serviceMock::sometimesFails);\n\n\t\t//then\n\t\tassertThat(future.get()).isEqualTo(\"Foo\");\n\t}\n\n\t@Test\n\tpublic void shouldRetryManyTimesIfFirstExecutionsThrowException() throws Exception {\n\t\t//given\n\t\tfinal RetryExecutor executor = new AsyncRetryExecutor(schedulerMock);\n\t\tgiven(serviceMock.sometimesFails()).\n\t\t\t\twillThrow(IllegalStateException.class, IllegalStateException.class, IllegalStateException.class).\n\t\t\t\twillReturn(\"Foo\");\n\n\t\t//when\n\t\texecutor.getWithRetry(serviceMock::sometimesFails);\n\n\t\t//then\n\t\tverify(serviceMock, times(4)).sometimesFails();\n\t}\n\n\t@Test\n\tpublic void shouldScheduleRetryWithDefaultDelay() throws Exception {\n\t\t//given\n\t\tfinal RetryExecutor executor = new AsyncRetryExecutor(schedulerMock);\n\t\tgiven(serviceMock.sometimesFails()).\n\t\t\t\twillThrow(IllegalStateException.class, IllegalStateException.class, IllegalStateException.class).\n\t\t\t\twillReturn(\"Foo\");\n\n\t\t//when\n\t\texecutor.getWithRetry(serviceMock::sometimesFails);\n\n\t\t//then\n\t\tfinal InOrder inOrder = inOrder(schedulerMock);\n\t\tinOrder.verify(schedulerMock).schedule(notNullRunnable(), eq(0L), millis());\n\t\tinOrder.verify(schedulerMock, times(3)).schedule(notNullRunnable(), eq(DEFAULT_PERIOD_MILLIS), millis());\n\t\tinOrder.verifyNoMoreInteractions();\n\t}\n\n\t@Test\n\tpublic void shouldPassCorrectRetryCountToEachInvocationInContext() throws Exception {\n\t\t//given\n\t\tfinal RetryExecutor executor = new AsyncRetryExecutor(schedulerMock);\n\t\tgiven(serviceMock.calculateSum(0)).willThrow(IllegalStateException.class);\n\t\tgiven(serviceMock.calculateSum(1)).willThrow(IllegalStateException.class);\n\t\tgiven(serviceMock.calculateSum(2)).willThrow(IllegalStateException.class);\n\t\tgiven(serviceMock.calculateSum(3)).willReturn(BigDecimal.ONE);\n\n\t\t//when\n\t\texecutor.getWithRetry(ctx -> serviceMock.calculateSum(ctx.getRetryCount()));\n\n\t\t//then\n\t\tfinal InOrder order = inOrder(serviceMock);\n\t\torder.verify(serviceMock).calculateSum(0);\n\t\torder.verify(serviceMock).calculateSum(1);\n\t\torder.verify(serviceMock).calculateSum(2);\n\t\torder.verify(serviceMock).calculateSum(3);\n\t\torder.verifyNoMoreInteractions();\n\t}\n\n}\n"
  },
  {
    "path": "src/test/java/com/nurkiewicz/asyncretry/AsyncRetryExecutorOneFailureTest.java",
    "content": "package com.nurkiewicz.asyncretry;\n\nimport com.nurkiewicz.asyncretry.policy.AbortRetryException;\nimport org.testng.annotations.Test;\n\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.atomic.AtomicReference;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown;\nimport static org.mockito.BDDMockito.given;\nimport static org.mockito.Mockito.verify;\n\n/**\n * @author Tomasz Nurkiewicz\n * @since 7/16/13, 10:51 PM\n */\npublic class AsyncRetryExecutorOneFailureTest extends AbstractBaseTestCase {\n\n\t@Test\n\tpublic void shouldNotRetryIfAbortThrown() throws Exception {\n\t\t//given\n\t\tfinal RetryExecutor executor = new AsyncRetryExecutor(schedulerMock);\n\t\tgiven(serviceMock.sometimesFails()).\n\t\t\t\twillThrow(AbortRetryException.class);\n\n\t\t//when\n\t\texecutor.getWithRetry(serviceMock::sometimesFails);\n\n\t\t//then\n\t\tverify(serviceMock).sometimesFails();\n\t}\n\n\t@Test\n\tpublic void shouldRethrowAbortExceptionIfFirstIterationThrownIt() throws Exception {\n\t\t//given\n\t\tfinal RetryExecutor executor = new AsyncRetryExecutor(schedulerMock);\n\t\tgiven(serviceMock.sometimesFails()).\n\t\t\t\twillThrow(AbortRetryException.class);\n\n\t\t//when\n\t\tfinal CompletableFuture<String> future = executor.getWithRetry(serviceMock::sometimesFails);\n\n\t\t//then\n\t\tassertThat(future.isCompletedExceptionally()).isTrue();\n\t\ttry {\n\t\t\tfuture.get();\n\t\t\tfailBecauseExceptionWasNotThrown(ExecutionException.class);\n\t\t} catch (ExecutionException e) {\n\t\t\tassertThat(e.getCause()).isInstanceOf(AbortRetryException.class);\n\t\t}\n\t}\n\n\t@Test\n\tpublic void shouldCompleteWithExceptionIfFirstIterationThrownIt() throws Exception {\n\t\t//given\n\t\tfinal RetryExecutor executor = new AsyncRetryExecutor(schedulerMock).dontRetry();\n\t\tgiven(serviceMock.sometimesFails()).\n\t\t\t\twillThrow(new IllegalStateException(DON_T_PANIC));\n\n\t\t//when\n\t\tfinal CompletableFuture<String> future = executor.getWithRetry(serviceMock::sometimesFails);\n\n\t\t//then\n\t\tAtomicReference<Throwable> error = new AtomicReference<>();\n\t\tfuture.whenComplete((res, t) -> {\n\t\t\tif (res == null) {\n\t\t\t\terror.set(t);       //schedulerMock is synchronous anyway\n\t\t\t}\n\t\t});\n\t\tassertThat(error.get()).\n\t\t\t\tisNotNull().\n\t\t\t\tisInstanceOf(IllegalStateException.class).\n\t\t\t\thasMessage(DON_T_PANIC);\n\t}\n\n\t@Test\n\tpublic void shouldRethrowLastThrownExceptionWhenAbortedInSubsequentIteration() throws Exception {\n\t\t//given\n\t\tfinal RetryExecutor executor = new AsyncRetryExecutor(schedulerMock);\n\t\tgiven(serviceMock.sometimesFails()).\n\t\t\t\twillThrow(\n\t\t\t\t\t\tnew IllegalArgumentException(\"First\"),\n\t\t\t\t\t\tnew IllegalStateException(\"Second\"),\n\t\t\t\t\t\tnew AbortRetryException()\n\t\t\t\t);\n\n\t\t//when\n\t\tfinal CompletableFuture<String> future = executor.getWithRetry(serviceMock::sometimesFails);\n\n\t\t//then\n\t\tassertThat(future.isCompletedExceptionally()).isTrue();\n\t\ttry {\n\t\t\tfuture.get();\n\t\t\tfailBecauseExceptionWasNotThrown(ExecutionException.class);\n\t\t} catch (ExecutionException e) {\n\t\t\tassertThat(e.getCause()).isInstanceOf(IllegalStateException.class);\n\t\t\tassertThat(e.getCause().getMessage()).isEqualTo(\"Second\");\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "src/test/java/com/nurkiewicz/asyncretry/AsyncRetryJobTest.java",
    "content": "package com.nurkiewicz.asyncretry;\n\nimport com.nurkiewicz.asyncretry.policy.AbortRetryException;\nimport org.assertj.core.api.Assertions;\nimport org.mockito.InOrder;\nimport org.testng.annotations.Test;\n\nimport java.io.IOException;\nimport java.net.SocketException;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.ExecutionException;\n\nimport static com.nurkiewicz.asyncretry.backoff.FixedIntervalBackoff.DEFAULT_PERIOD_MILLIS;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Fail.failBecauseExceptionWasNotThrown;\nimport static org.mockito.BDDMockito.given;\nimport static org.mockito.Matchers.eq;\nimport static org.mockito.Mockito.inOrder;\nimport static org.mockito.Mockito.times;\n\n/**\n * @author Tomasz Nurkiewicz\n * @since 7/21/13, 7:07 PM\n */\npublic class AsyncRetryJobTest extends AbstractBaseTestCase {\n\n\t@Test\n\tpublic void shouldUnwrapUserFutureAndReturnIt() throws Exception {\n\t\t//given\n\t\tfinal RetryExecutor executor = new AsyncRetryExecutor(schedulerMock);\n\t\tgiven(serviceMock.safeAsync()).willReturn(CompletableFuture.completedFuture(\"42\"));\n\n\t\t//when\n\t\tfinal CompletableFuture<String> future = executor.getFutureWithRetry(ctx -> serviceMock.safeAsync());\n\n\t\t//then\n\t\tassertThat(future.get()).isEqualTo(\"42\");\n\t}\n\n\t@Test\n\tpublic void shouldSucceedAfterFewAsynchronousRetries() throws Exception {\n\t\t//given\n\t\tfinal RetryExecutor executor = new AsyncRetryExecutor(schedulerMock);\n\t\tgiven(serviceMock.safeAsync()).willReturn(\n\t\t\t\tfailedAsync(new SocketException(\"First\")),\n\t\t\t\tfailedAsync(new IOException(\"Second\")),\n\t\t\t\tCompletableFuture.completedFuture(\"42\")\n\t\t);\n\n\t\t//when\n\t\tfinal CompletableFuture<String> future = executor.getFutureWithRetry(ctx -> serviceMock.safeAsync());\n\n\t\t//then\n\t\tassertThat(future.get()).isEqualTo(\"42\");\n\t}\n\n\t@Test\n\tpublic void shouldScheduleTwoTimesWhenRetries() throws Exception {\n\t\t//given\n\t\tfinal RetryExecutor executor = new AsyncRetryExecutor(schedulerMock);\n\t\tgiven(serviceMock.safeAsync()).willReturn(\n\t\t\t\tfailedAsync(new SocketException(\"First\")),\n\t\t\t\tfailedAsync(new IOException(\"Second\")),\n\t\t\t\tCompletableFuture.completedFuture(\"42\")\n\t\t);\n\n\t\t//when\n\t\tfinal CompletableFuture<String> future = executor.getFutureWithRetry(ctx -> serviceMock.safeAsync());\n\n\t\t//then\n\t\tfuture.get();\n\n\t\tfinal InOrder order = inOrder(schedulerMock);\n\t\torder.verify(schedulerMock).schedule(notNullRunnable(), eq(0L), millis());\n\t\torder.verify(schedulerMock, times(2)).schedule(notNullRunnable(), eq(DEFAULT_PERIOD_MILLIS), millis());\n\t}\n\n\tprivate CompletableFuture<String> failedAsync(Throwable throwable) {\n\t\tfinal CompletableFuture<String> future = new CompletableFuture<>();\n\t\tfuture.completeExceptionally(throwable);\n\t\treturn future;\n\t}\n\n\t@Test\n\tpublic void shouldRethrowOriginalExceptionFromUserFutureCompletion() throws Exception {\n\t\t//given\n\t\tfinal RetryExecutor executor = new AsyncRetryExecutor(schedulerMock).\n\t\t\t\tabortOn(SocketException.class);\n\t\tgiven(serviceMock.safeAsync()).willReturn(\n\t\t\t\tfailedAsync(new SocketException(DON_T_PANIC))\n\t\t);\n\n\t\t//when\n\t\tfinal CompletableFuture<String> future = executor.getFutureWithRetry(ctx -> serviceMock.safeAsync());\n\n\t\t//then\n\t\tassertThat(future.isCompletedExceptionally()).isTrue();\n\n\t\ttry {\n\t\t\tfuture.get();\n\t\t\tfailBecauseExceptionWasNotThrown(ExecutionException.class);\n\t\t} catch (ExecutionException e) {\n\t\t\tfinal Throwable cause = e.getCause();\n\t\t\tassertThat(cause).isInstanceOf(SocketException.class);\n\t\t\tassertThat(cause).hasMessage(DON_T_PANIC);\n\t\t}\n\t}\n\n\t@Test\n\tpublic void shouldRethrowOriginalExceptionFromUserFutureCompletionAndAbortWhenTestFails() throws Exception {\n\t\t//given\n\t\tfinal RetryExecutor executor = new AsyncRetryExecutor(schedulerMock).\n\t\t\t\tabortIf(t -> { throw new RuntimeException(\"test invalid\"); });\n\n\t\tgiven(serviceMock.safeAsync()).willReturn(\n\t\t\t\tfailedAsync(new SocketException(DON_T_PANIC))\n\t\t);\n\n\t\t//when\n\t\tfinal CompletableFuture<String> future = executor.getFutureWithRetry(ctx -> serviceMock.safeAsync());\n\n\t\t//then\n\t\tassertThat(future.isCompletedExceptionally()).isTrue();\n\n\t\ttry {\n\t\t\tfuture.get();\n\t\t\tfailBecauseExceptionWasNotThrown(ExecutionException.class);\n\t\t} catch (ExecutionException e) {\n\t\t\tfinal Throwable cause = e.getCause();\n\t\t\tassertThat(cause).isInstanceOf(SocketException.class);\n\t\t\tassertThat(cause).hasMessage(DON_T_PANIC);\n\t\t}\n\t}\n\n\t@Test\n\tpublic void shouldAbortWhenTargetFutureWantsToAbort() throws Exception {\n\t\t//given\n\t\tfinal RetryExecutor executor = new AsyncRetryExecutor(schedulerMock);\n\t\tgiven(serviceMock.safeAsync()).willReturn(\n\t\t\t\tfailedAsync(new AbortRetryException())\n\t\t);\n\n\t\t//when\n\t\tfinal CompletableFuture<String> future = executor.getFutureWithRetry(ctx -> serviceMock.safeAsync());\n\n\t\t//then\n\t\tassertThat(future.isCompletedExceptionally()).isTrue();\n\n\t\ttry {\n\t\t\tfuture.get();\n\t\t\tfailBecauseExceptionWasNotThrown(ExecutionException.class);\n\t\t} catch (ExecutionException e) {\n\t\t\tfinal Throwable cause = e.getCause();\n\t\t\tassertThat(cause).isInstanceOf(AbortRetryException.class);\n\t\t}\n\t}\n\n\t@Test\n\tpublic void shouldRethrowExceptionThatWasThrownFromUserTaskBeforeReturningFuture() throws Exception {\n\t\t//given\n\t\tfinal RetryExecutor executor = new AsyncRetryExecutor(schedulerMock).\n\t\t\t\tabortOn(IllegalArgumentException.class);\n\t\tgiven(serviceMock.safeAsync()).willThrow(new IllegalArgumentException(DON_T_PANIC));\n\n\t\t//when\n\t\tfinal CompletableFuture<String> future = executor.getFutureWithRetry(ctx -> serviceMock.safeAsync());\n\n\t\t//then\n\t\tassertThat(future.isCompletedExceptionally()).isTrue();\n\n\t\ttry {\n\t\t\tfuture.get();\n\t\t\tAssertions.failBecauseExceptionWasNotThrown(ExecutionException.class);\n\t\t} catch (ExecutionException e) {\n\t\t\tassertThat(e.getCause()).isInstanceOf(IllegalArgumentException.class);\n\t\t\tassertThat(e.getCause()).hasMessage(DON_T_PANIC);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "src/test/java/com/nurkiewicz/asyncretry/FaultyService.java",
    "content": "package com.nurkiewicz.asyncretry;\n\nimport java.math.BigDecimal;\nimport java.util.concurrent.CompletableFuture;\n\n/**\n * @author Tomasz Nurkiewicz\n * @since 7/17/13, 7:09 PM\n */\npublic interface FaultyService {\n\n\tint alwaysSucceeds();\n\n\tString sometimesFails();\n\n\tBigDecimal calculateSum(int retry);\n\n\tvoid withFlag(boolean flag);\n\n\tCompletableFuture<String> safeAsync();\n\n\tCompletableFuture<String> alwaysFailsAsync();\n\n}\n"
  },
  {
    "path": "src/test/java/com/nurkiewicz/asyncretry/SyncRetryExecutorTest.java",
    "content": "package com.nurkiewicz.asyncretry;\n\nimport com.nurkiewicz.asyncretry.function.RetryCallable;\nimport com.nurkiewicz.asyncretry.function.RetryRunnable;\nimport org.testng.annotations.Test;\n\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.atomic.AtomicReference;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown;\n\npublic class SyncRetryExecutorTest extends AbstractBaseTestCase {\n\n\tRetryExecutor executor = SyncRetryExecutor.INSTANCE;\n\n\t@Test\n\tvoid shouldReturnCompletedFutureWhenDoWithRetryCalled() throws ExecutionException, InterruptedException {\n\t\t//given\n\t\tString mainThread = Thread.currentThread().getName();\n\t\tAtomicReference<String> poolThread = new AtomicReference<>();\n\n\t\t//when\n\t\tCompletableFuture<Void> result = executor.doWithRetry(ctx -> poolThread.set(Thread.currentThread().getName()));\n\n\t\t//then\n\t\tassertThat(poolThread.get()).isEqualTo(mainThread);\n\t}\n\n\t@Test\n\tvoid shouldWrapExceptionInFutureRatherThanThrowingIt() throws InterruptedException {\n\t\t//given\n\t\tRetryRunnable block = context -> {\n\t\t\tthrow new IllegalArgumentException(DON_T_PANIC);\n\t\t};\n\t\tCompletableFuture<Void> future = executor.doWithRetry(block);\n\n\t\ttry {\n\t\t\t//when\n\t\t\tfuture.get();\n\t\t\tfailBecauseExceptionWasNotThrown(ExecutionException.class);\n\t\t} catch (ExecutionException e) {\n\t\t\t//then\n\t\t\tassertThat(e).hasCauseInstanceOf(IllegalArgumentException.class);\n\t\t\tassertThat(e.getCause()).hasMessage(DON_T_PANIC);\n\t\t}\n\t}\n\n\t@Test\n\tvoid shouldReturnCompletedFutureWhenGetWithRetryCalled() throws ExecutionException, InterruptedException {\n\t\t//given\n\t\tString mainThread = Thread.currentThread().getName();\n\n\t\t//when\n\t\tCompletableFuture<String> result = executor.getWithRetry(() -> Thread.currentThread().getName());\n\n\t\t//then\n\t\tassertThat(result.get()).isEqualTo(mainThread);\n\t}\n\n\t@Test\n\tvoid shouldWrapExceptionInFutureRatherThanThrowingItInGetWithRetry() throws InterruptedException {\n\t\t//given\n\t\tCallable<Void> block = () -> {\n\t\t\tthrow new IllegalArgumentException(DON_T_PANIC);\n\t\t};\n\t\tCompletableFuture<Void> future = executor.getWithRetry(block);\n\n\t\ttry {\n\t\t\t//when\n\t\t\tfuture.get();\n\t\t\tfailBecauseExceptionWasNotThrown(ExecutionException.class);\n\t\t} catch (ExecutionException e) {\n\t\t\t//then\n\t\t\tassertThat(e).hasCauseInstanceOf(IllegalArgumentException.class);\n\t\t\tassertThat(e.getCause()).hasMessage(DON_T_PANIC);\n\t\t}\n\t}\n\n\t@Test\n\tvoid shouldReturnCompletedFutureWhenGetWithRetryCalledContext() throws ExecutionException, InterruptedException {\n\t\t//given\n\t\tString mainThread = Thread.currentThread().getName();\n\n\t\t//when\n\t\tCompletableFuture<String> result = executor.getWithRetry(ctx -> Thread.currentThread().getName());\n\n\t\t//then\n\t\tassertThat(result.get()).isEqualTo(mainThread);\n\t}\n\n\t@Test\n\tvoid shouldWrapExceptionInFutureRatherThanThrowingItInGetWithRetryContext() throws InterruptedException {\n\t\t//given\n\t\tRetryCallable<Void> block = ctx -> {\n\t\t\tthrow new IllegalArgumentException(DON_T_PANIC);\n\t\t};\n\t\tCompletableFuture<Void> future = executor.getWithRetry(block);\n\n\t\ttry {\n\t\t\t//when\n\t\t\tfuture.get();\n\t\t\tfailBecauseExceptionWasNotThrown(ExecutionException.class);\n\t\t} catch (ExecutionException e) {\n\t\t\t//then\n\t\t\tassertThat(e).hasCauseInstanceOf(IllegalArgumentException.class);\n\t\t\tassertThat(e.getCause()).hasMessage(DON_T_PANIC);\n\t\t}\n\t}\n\n\t@Test\n\tvoid shouldReturnCompletedFutureWhenGetWithRetryOnFutureCalled() throws ExecutionException, InterruptedException {\n\t\t//given\n\t\tString mainThread = Thread.currentThread().getName();\n\n\t\t//when\n\t\tCompletableFuture<String> result = executor.getFutureWithRetry(ctx ->\n\t\t\t\tCompletableFuture.completedFuture(Thread.currentThread().getName()));\n\n\t\t//then\n\t\tassertThat(result.get()).isEqualTo(mainThread);\n\t}\n\n\t@Test\n\tvoid shouldWrapExceptionInFutureRatherThanThrowingItInGetWithRetryOnFuture() throws InterruptedException {\n\t\t//given\n\t\tRetryCallable<CompletableFuture<String>> block = ctx -> {\n\t\t\tfinal CompletableFuture<String> failedFuture = new CompletableFuture<>();\n\t\t\tfailedFuture.completeExceptionally(new IllegalArgumentException(DON_T_PANIC));\n\t\t\treturn failedFuture;\n\t\t};\n\t\tCompletableFuture<String> future = executor.getFutureWithRetry(block);\n\n\t\ttry {\n\t\t\t//when\n\t\t\tfuture.get();\n\t\t\tfailBecauseExceptionWasNotThrown(ExecutionException.class);\n\t\t} catch (ExecutionException e) {\n\t\t\t//then\n\t\t\tassertThat(e).hasCauseInstanceOf(IllegalArgumentException.class);\n\t\t\tassertThat(e.getCause()).hasMessage(DON_T_PANIC);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "src/test/java/com/nurkiewicz/asyncretry/backoff/BoundedMaxBackoffTest.java",
    "content": "package com.nurkiewicz.asyncretry.backoff;\n\nimport com.nurkiewicz.asyncretry.AbstractBaseTestCase;\nimport org.testng.annotations.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * @author Tomasz Nurkiewicz\n * @since 7/20/13, 5:44 PM\n */\npublic class BoundedMaxBackoffTest extends AbstractBaseTestCase {\n\n\t@Test\n\tpublic void shouldReturnOriginalBackoffDelayIfBelowMax() throws Exception {\n\t\tfinal Backoff backoff = new ExponentialDelayBackoff(1, 2.0).withMaxDelay();\n\n\t\tassertThat(backoff.delayMillis(retry(1))).isEqualTo(1);\n\t\tassertThat(backoff.delayMillis(retry(2))).isEqualTo(2);\n\t\tassertThat(backoff.delayMillis(retry(3))).isEqualTo(4);\n\t\tassertThat(backoff.delayMillis(retry(4))).isEqualTo(8);\n\t}\n\n\t@Test\n\tpublic void shouldCapBackoffAtDefaultLevel() throws Exception {\n\t\tfinal Backoff backoff = new ExponentialDelayBackoff(1, 2.0).withMaxDelay();\n\n\t\tassertThat(backoff.delayMillis(retry(100))).isEqualTo(BoundedMaxBackoff.DEFAULT_MAX_DELAY_MILLIS);\n\t}\n\n\t@Test\n\tpublic void shouldCapBackoffAtGivenLevel() throws Exception {\n\t\tfinal Backoff backoff = new ExponentialDelayBackoff(1, 2.0).withMaxDelay(1234);\n\n\t\tassertThat(backoff.delayMillis(retry(100))).isEqualTo(1234);\n\t}\n\n}\n"
  },
  {
    "path": "src/test/java/com/nurkiewicz/asyncretry/backoff/BoundedMinBackoffTest.java",
    "content": "package com.nurkiewicz.asyncretry.backoff;\n\nimport com.nurkiewicz.asyncretry.AbstractBaseTestCase;\nimport org.testng.annotations.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * @author Tomasz Nurkiewicz\n * @since 7/20/13, 6:32 PM\n */\npublic class BoundedMinBackoffTest extends AbstractBaseTestCase {\n\n\t@Test\n\tpublic void shouldReturnOriginalBackoffDelayIfAboveMin() throws Exception {\n\t\tfinal Backoff backoff = new ExponentialDelayBackoff(1000, 2.0).withMinDelay();\n\n\t\tassertThat(backoff.delayMillis(retry(1))).isEqualTo(1000);\n\t\tassertThat(backoff.delayMillis(retry(2))).isEqualTo(2000);\n\t\tassertThat(backoff.delayMillis(retry(3))).isEqualTo(4000);\n\t\tassertThat(backoff.delayMillis(retry(4))).isEqualTo(8000);\n\t}\n\n\t@Test\n\tpublic void shouldCapBackoffAtDefaultLevel() throws Exception {\n\t\tfinal Backoff backoff = new ExponentialDelayBackoff(1, 2.0).withMinDelay();\n\n\t\tassertThat(backoff.delayMillis(retry(1))).isEqualTo(BoundedMinBackoff.DEFAULT_MIN_DELAY_MILLIS);\n\t}\n\n\t@Test\n\tpublic void shouldCapBackoffAtGivenLevel() throws Exception {\n\t\tfinal Backoff backoff = new ExponentialDelayBackoff(1, 2.0).withMaxDelay(250);\n\n\t\tassertThat(backoff.delayMillis(retry(100))).isEqualTo(250);\n\t}\n\n\t@Test\n\tpublic void shouldApplyBothMinAndMaxBound() throws Exception {\n\t\tfinal Backoff backoff = new ExponentialDelayBackoff(1, 2.0).\n\t\t\t\twithMinDelay(5).\n\t\t\t\twithMaxDelay(10);\n\n\t\tassertThat(backoff.delayMillis(retry(2))).isEqualTo(5);\n\t\tassertThat(backoff.delayMillis(retry(3))).isEqualTo(5);\n\t\tassertThat(backoff.delayMillis(retry(4))).isEqualTo(8);\n\t\tassertThat(backoff.delayMillis(retry(5))).isEqualTo(10);\n\t}\n\n}\n"
  },
  {
    "path": "src/test/java/com/nurkiewicz/asyncretry/backoff/ExponentialDelayBackoffTest.java",
    "content": "package com.nurkiewicz.asyncretry.backoff;\n\nimport com.nurkiewicz.asyncretry.AbstractBaseTestCase;\nimport org.testng.annotations.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown;\n\n/**\n * @author Tomasz Nurkiewicz\n * @since 7/20/13, 5:30 PM\n */\npublic class ExponentialDelayBackoffTest extends AbstractBaseTestCase {\n\n\t@Test\n\tpublic void shouldThrowWhenNotPositiveInitialDelay() throws Exception {\n\t\t//given\n\t\tfinal int initialDelayMillis = 0;\n\n\t\ttry {\n\t\t\t//when\n\t\t\tnew ExponentialDelayBackoff(initialDelayMillis, 2.0);\n\t\t\tfailBecauseExceptionWasNotThrown(IllegalArgumentException.class);\n\t\t} catch (IllegalArgumentException e) {\n\t\t\t//then\n\t\t\tassertThat(e.getMessage()).endsWith(\"0\");\n\t\t}\n\n\t}\n\n\t@Test\n\tpublic void shouldReturnPowersOfTwo() throws Exception {\n\t\t//given\n\t\tfinal ExponentialDelayBackoff backoff = new ExponentialDelayBackoff(1, 2.0);\n\n\t\t//when\n\t\tfinal long first = backoff.delayMillis(retry(1));\n\t\tfinal long second = backoff.delayMillis(retry(2));\n\t\tfinal long third = backoff.delayMillis(retry(3));\n\t\tfinal long fourth = backoff.delayMillis(retry(4));\n\n\t\t//then\n\t\tassertThat(first).isEqualTo(1);\n\t\tassertThat(second).isEqualTo(2);\n\t\tassertThat(third).isEqualTo(2 * 2);\n\t\tassertThat(fourth).isEqualTo(2 * 2 * 2);\n\t}\n\n}\n"
  },
  {
    "path": "src/test/java/com/nurkiewicz/asyncretry/backoff/FirstRetryNoDelayBackoffTest.java",
    "content": "package com.nurkiewicz.asyncretry.backoff;\n\nimport com.nurkiewicz.asyncretry.AbstractBaseTestCase;\nimport org.testng.annotations.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\npublic class FirstRetryNoDelayBackoffTest extends AbstractBaseTestCase {\n\n\t@Test\n\tpublic void firstRetryShouldHaveNoDelay() {\n\t\t//given\n\t\tfinal Backoff backoff = new FixedIntervalBackoff(1_000).withFirstRetryNoDelay();\n\n\t\t//when\n\t\tfinal long first = backoff.delayMillis(retry(1));\n\t\tfinal long second = backoff.delayMillis(retry(2));\n\t\tfinal long third = backoff.delayMillis(retry(3));\n\n\t\t//then\n\t\tassertThat(first).isEqualTo(0);\n\t\tassertThat(second).isEqualTo(1_000);\n\t\tassertThat(third).isEqualTo(1_000);\n\t}\n\n\t@Test\n\tpublic void secondRetryShouldCalculateDelayAsIfItWasFirst() {\n\t\t//given\n\t\tfinal Backoff backoff = new ExponentialDelayBackoff(100, 2).withFirstRetryNoDelay();\n\n\t\t//when\n\t\tfinal long first = backoff.delayMillis(retry(1));\n\t\tfinal long second = backoff.delayMillis(retry(2));\n\t\tfinal long third = backoff.delayMillis(retry(3));\n\n\t\t//then\n\t\tassertThat(first).isEqualTo(0);\n\t\tassertThat(second).isEqualTo(100);\n\t\tassertThat(third).isEqualTo(200);\n\t}\n\n}"
  },
  {
    "path": "src/test/java/com/nurkiewicz/asyncretry/backoff/RandomBackoffTest.java",
    "content": "package com.nurkiewicz.asyncretry.backoff;\n\nimport com.nurkiewicz.asyncretry.AbstractBaseTestCase;\nimport org.mockito.Mock;\nimport org.testng.annotations.Test;\n\nimport java.util.Random;\n\nimport static com.nurkiewicz.asyncretry.backoff.FixedIntervalBackoff.DEFAULT_PERIOD_MILLIS;\nimport static com.nurkiewicz.asyncretry.backoff.UniformRandomBackoff.DEFAULT_RANDOM_RANGE_MILLIS;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.BDDMockito.given;\n\n/**\n * @author Tomasz Nurkiewicz\n * @since 7/20/13, 6:57 PM\n */\npublic class RandomBackoffTest extends AbstractBaseTestCase {\n\n\t@Mock\n\tprivate Random randomMock;\n\n\t@Test\n\tpublic void shouldApplyRandomUniformDistributionWithDefaultRange() throws Exception {\n\t\t//given\n\t\tfinal Backoff backoff = new FixedIntervalBackoff().withUniformJitter();\n\n\t\t//when\n\t\tfinal long delay = backoff.delayMillis(anyRetry());\n\n\t\t//then\n\t\tassertThat(delay).\n\t\t\t\tisGreaterThanOrEqualTo(DEFAULT_PERIOD_MILLIS - DEFAULT_RANDOM_RANGE_MILLIS).\n\t\t\t\tisLessThanOrEqualTo(DEFAULT_PERIOD_MILLIS + DEFAULT_RANDOM_RANGE_MILLIS);\n\t}\n\n\t@Test\n\tpublic void shouldApplyRandomUniformDistribution() throws Exception {\n\t\t//given\n\t\tfinal int range = 300;\n\t\tfinal Backoff backoff = new FixedIntervalBackoff().withUniformJitter(range);\n\n\t\t//when\n\t\tfinal long delay = backoff.delayMillis(anyRetry());\n\n\t\t//then\n\t\tassertThat(delay).\n\t\t\t\tisGreaterThanOrEqualTo(DEFAULT_PERIOD_MILLIS - range).\n\t\t\t\tisLessThanOrEqualTo(DEFAULT_PERIOD_MILLIS + range);\n\t}\n\n\t@Test\n\tpublic void shouldApplyRandomUniformDistributionWithCustomRandomSource() throws Exception {\n\t\t//given\n\t\tfinal Backoff backoff = new UniformRandomBackoff(new FixedIntervalBackoff(), randomMock);\n\t\tgiven(randomMock.nextDouble()).willReturn(0.5);\n\n\t\t//when\n\t\tfinal long delay = backoff.delayMillis(anyRetry());\n\n\t\t//then\n\t\tassertThat(delay).isEqualTo(DEFAULT_PERIOD_MILLIS);\n\t}\n\n\t@Test\n\tpublic void shouldApplyRandomProportionalDistributionWithDefaultRange() throws Exception {\n\t\t//given\n\t\tfinal Backoff backoff = new FixedIntervalBackoff().withProportionalJitter();\n\n\t\t//when\n\t\tfinal long delay = backoff.delayMillis(anyRetry());\n\n\t\t//then\n\t\tassertThat(delay).\n\t\t\t\tisGreaterThanOrEqualTo((long) (DEFAULT_PERIOD_MILLIS * (1 - ProportionalRandomBackoff.DEFAULT_MULTIPLIER))).\n\t\t\t\tisLessThan((long) (DEFAULT_PERIOD_MILLIS * (1 + ProportionalRandomBackoff.DEFAULT_MULTIPLIER)));\n\t}\n\n\t@Test\n\tpublic void shouldApplyRandomProportionalDistribution() throws Exception {\n\t\t//given\n\t\tfinal double range = 0.3;\n\t\tfinal Backoff backoff = new FixedIntervalBackoff().withProportionalJitter(range);\n\n\t\t//when\n\t\tfinal long delay = backoff.delayMillis(anyRetry());\n\n\t\t//then\n\t\tassertThat(delay).\n\t\t\t\tisGreaterThanOrEqualTo((long) (DEFAULT_PERIOD_MILLIS * (1 - range))).\n\t\t\t\tisLessThan((long) (DEFAULT_PERIOD_MILLIS * (1 + range)));\n\t}\n\n\t@Test\n\tpublic void shouldApplyRandomProportionalDistributionWithCustomRandomSource() throws Exception {\n\t\t//given\n\t\tfinal Backoff backoff = new ProportionalRandomBackoff(new FixedIntervalBackoff(), randomMock);\n\t\tgiven(randomMock.nextDouble()).willReturn(0.5);\n\n\t\t//when\n\t\tfinal long delay = backoff.delayMillis(anyRetry());\n\n\t\t//then\n\t\tassertThat(delay).isEqualTo(DEFAULT_PERIOD_MILLIS);\n\t}\n\n}\n"
  },
  {
    "path": "src/test/java/com/nurkiewicz/asyncretry/policy/AbstractRetryPolicyTest.java",
    "content": "package com.nurkiewicz.asyncretry.policy;\n\nimport com.nurkiewicz.asyncretry.AbstractBaseTestCase;\nimport com.nurkiewicz.asyncretry.AsyncRetryContext;\n\n/**\n * @author Tomasz Nurkiewicz\n * @since 7/18/13, 11:27 PM\n */\npublic class AbstractRetryPolicyTest extends AbstractBaseTestCase {\n\n\tprivate static final int ANY_RETRY = 7;\n\n\tprotected boolean shouldRetryOn(RetryPolicy policy, Throwable lastThrowable) {\n\t\treturn policy.shouldContinue(new AsyncRetryContext(policy, ANY_RETRY, lastThrowable));\n\t}\n}\n\nclass OptimisticLockException extends RuntimeException {}"
  },
  {
    "path": "src/test/java/com/nurkiewicz/asyncretry/policy/RetryPolicyBlackListTest.java",
    "content": "package com.nurkiewicz.asyncretry.policy;\n\nimport org.testng.annotations.Test;\n\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.net.ConnectException;\nimport java.net.SocketException;\nimport java.util.concurrent.TimeoutException;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * @author Tomasz Nurkiewicz\n * @since 7/18/13, 11:25 PM\n */\npublic class RetryPolicyBlackListTest extends AbstractRetryPolicyTest {\n\n\t@Test\n\tpublic void shouldAbortOnSpecifiedException() throws Exception {\n\t\tfinal RetryPolicy policy = new RetryPolicy().abortOn(ConnectException.class);\n\n\t\tassertThat(shouldRetryOn(policy, new ConnectException())).isFalse();\n\t}\n\n\t@Test\n\tpublic void shouldRetryIfExceptionNotAborting() throws Exception {\n\t\tfinal RetryPolicy policy = new RetryPolicy().abortOn(ConnectException.class);\n\n\t\tassertThat(shouldRetryOn(policy, new Exception())).isTrue();\n\t\tassertThat(shouldRetryOn(policy, new RuntimeException())).isTrue();\n\t\tassertThat(shouldRetryOn(policy, new IOException())).isTrue();\n\t\tassertThat(shouldRetryOn(policy, new SocketException())).isTrue();\n\t\tassertThat(shouldRetryOn(policy, new ClassCastException())).isTrue();\n\t\tassertThat(shouldRetryOn(policy, new NullPointerException())).isTrue();\n\t\tassertThat(shouldRetryOn(policy, new IllegalArgumentException())).isTrue();\n\t\tassertThat(shouldRetryOn(policy, new IllegalStateException())).isTrue();\n\t\tassertThat(shouldRetryOn(policy, new TimeoutException())).isTrue();\n\t}\n\n\t@Test\n\tpublic void shouldRetryIfErrorNotAborting() throws Exception {\n\t\tfinal RetryPolicy policy = new RetryPolicy().abortOn(ConnectException.class);\n\n\t\tassertThat(shouldRetryOn(policy, new OutOfMemoryError())).isTrue();\n\t\tassertThat(shouldRetryOn(policy, new StackOverflowError())).isTrue();\n\t\tassertThat(shouldRetryOn(policy, new NoClassDefFoundError())).isTrue();\n\t}\n\n\t@Test\n\tpublic void shouldAbortIfBlackListedException() throws Exception {\n\t\tfinal RetryPolicy policy = new RetryPolicy().\n\t\t\t\tabortOn(NullPointerException.class);\n\n\t\tassertThat(shouldRetryOn(policy, new NullPointerException())).isFalse();\n\t}\n\n\t@Test\n\tpublic void shouldAbortOnSubclassesOfBlackListedException() throws Exception {\n\t\tfinal RetryPolicy policy = new RetryPolicy().abortOn(IOException.class);\n\n\t\tassertThat(shouldRetryOn(policy, new FileNotFoundException())).isFalse();\n\t\tassertThat(shouldRetryOn(policy, new SocketException())).isFalse();\n\t\tassertThat(shouldRetryOn(policy, new ConnectException())).isFalse();\n\t}\n\n\t@Test\n\tpublic void shouldAbortOnAnyBlackListedExceptions() throws Exception {\n\t\tfinal RetryPolicy policy = new RetryPolicy().\n\t\t\t\tabortOn(NullPointerException.class).\n\t\t\t\tabortOn(OutOfMemoryError.class).\n\t\t\t\tabortOn(StackOverflowError.class);\n\n\t\tassertThat(shouldRetryOn(policy, new NullPointerException())).isFalse();\n\t\tassertThat(shouldRetryOn(policy, new OutOfMemoryError())).isFalse();\n\t\tassertThat(shouldRetryOn(policy, new StackOverflowError())).isFalse();\n\t}\n\n\t@Test\n\tpublic void shouldAbortOnAnyBlackListedExceptionsInOneList() throws Exception {\n\t\tfinal RetryPolicy policy = new RetryPolicy().\n\t\t\t\tabortOn(NullPointerException.class, OutOfMemoryError.class, StackOverflowError.class);\n\n\t\tassertThat(shouldRetryOn(policy, new NullPointerException())).isFalse();\n\t\tassertThat(shouldRetryOn(policy, new OutOfMemoryError())).isFalse();\n\t\tassertThat(shouldRetryOn(policy, new StackOverflowError())).isFalse();\n\t}\n\n\t@Test\n\tpublic void shouldAbortOnSubclassesOfAnyOfBlackListedExceptions() throws Exception {\n\t\tfinal RetryPolicy policy = new RetryPolicy().\n\t\t\t\tabortOn(IOException.class).\n\t\t\t\tabortOn(RuntimeException.class);\n\n\t\tassertThat(shouldRetryOn(policy, new FileNotFoundException())).isFalse();\n\t\tassertThat(shouldRetryOn(policy, new ConnectException())).isFalse();\n\t\tassertThat(shouldRetryOn(policy, new NullPointerException())).isFalse();\n\t}\n\n\t@Test\n\tpublic void shouldAbortOnSubclassesOfAnyOfBlackListedExceptionsInOneList() throws Exception {\n\t\tfinal RetryPolicy policy = new RetryPolicy().\n\t\t\t\tabortOn(IOException.class, RuntimeException.class);\n\n\t\tassertThat(shouldRetryOn(policy, new FileNotFoundException())).isFalse();\n\t\tassertThat(shouldRetryOn(policy, new ConnectException())).isFalse();\n\t\tassertThat(shouldRetryOn(policy, new NullPointerException())).isFalse();\n\t}\n\n}\n"
  },
  {
    "path": "src/test/java/com/nurkiewicz/asyncretry/policy/RetryPolicyBothBlackAndWhiteTest.java",
    "content": "package com.nurkiewicz.asyncretry.policy;\n\nimport org.testng.annotations.Test;\n\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.net.ConnectException;\nimport java.net.SocketException;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * @author Tomasz Nurkiewicz\n * @since 7/18/13, 11:25 PM\n */\npublic class RetryPolicyBothBlackAndWhiteTest extends AbstractRetryPolicyTest {\n\n\t@Test\n\tpublic void shouldRetryOnGivenException() throws Exception {\n\t\tfinal RetryPolicy policy = new RetryPolicy().\n\t\t\t\tretryOn(IOException.class).\n\t\t\t\tabortOn(NullPointerException.class);\n\n\t\tassertThat(shouldRetryOn(policy, new NullPointerException())).isFalse();\n\t\tassertThat(shouldRetryOn(policy, new IOException())).isTrue();\n\t\tassertThat(shouldRetryOn(policy, new ConnectException())).isTrue();\n\t}\n\n\t@Test\n\tpublic void shouldAbortOnGivenException() throws Exception {\n\t\tfinal RetryPolicy policy = new RetryPolicy().\n\t\t\t\tabortOn(IOException.class).\n\t\t\t\tretryOn(NullPointerException.class);\n\n\t\tassertThat(shouldRetryOn(policy, new NullPointerException())).isTrue();\n\t\tassertThat(shouldRetryOn(policy, new IOException())).isFalse();\n\t\tassertThat(shouldRetryOn(policy, new ConnectException())).isFalse();\n\t}\n\n\t@Test\n\tpublic void shouldRetryUnlessGivenSubclass() throws Exception {\n\t\tfinal RetryPolicy policy = new RetryPolicy().\n\t\t\t\tretryOn(IOException.class).\n\t\t\t\tabortOn(FileNotFoundException.class);\n\n\t\tassertThat(shouldRetryOn(policy, new IOException())).isTrue();\n\t\tassertThat(shouldRetryOn(policy, new SocketException())).isTrue();\n\t\tassertThat(shouldRetryOn(policy, new ConnectException())).isTrue();\n\t\tassertThat(shouldRetryOn(policy, new FileNotFoundException())).isFalse();\n\t}\n\n\t@Test\n\tpublic void shouldRetryUnlessGivenSubclassWithReversedDeclarationOrder() throws Exception {\n\t\tfinal RetryPolicy policy = new RetryPolicy().\n\t\t\t\tabortOn(FileNotFoundException.class).\n\t\t\t\tretryOn(IOException.class);\n\n\t\tassertThat(shouldRetryOn(policy, new IOException())).isTrue();\n\t\tassertThat(shouldRetryOn(policy, new SocketException())).isTrue();\n\t\tassertThat(shouldRetryOn(policy, new ConnectException())).isTrue();\n\t\tassertThat(shouldRetryOn(policy, new FileNotFoundException())).isFalse();\n\t}\n\n\t@Test\n\tpublic void shouldUnderstandManyWhiteAndBlackListedExceptions() throws Exception {\n\t\tfinal RetryPolicy policy = new RetryPolicy().\n\t\t\t\tretryOn(Exception.class).\n\t\t\t\tretryOn(LinkageError.class).\n\t\t\t\tabortOn(IncompatibleClassChangeError.class).\n\t\t\t\tabortOn(ClassCastException.class).\n\t\t\t\tabortOn(ConnectException.class);\n\n\t\tassertThat(shouldRetryOn(policy, new Exception())).isTrue();\n\t\tassertThat(shouldRetryOn(policy, new IOException())).isTrue();\n\t\tassertThat(shouldRetryOn(policy, new IllegalStateException())).isTrue();\n\t\tassertThat(shouldRetryOn(policy, new NoClassDefFoundError())).isTrue();\n\t\tassertThat(shouldRetryOn(policy, new UnsupportedClassVersionError())).isTrue();\n\n\t\tassertThat(shouldRetryOn(policy, new NoSuchFieldError())).isFalse();\n\t\tassertThat(shouldRetryOn(policy, new OutOfMemoryError())).isFalse();\n\t\tassertThat(shouldRetryOn(policy, new ClassCastException())).isFalse();\n\t\tassertThat(shouldRetryOn(policy, new ConnectException())).isFalse();\n\t}\n\n\t@Test\n\tpublic void shouldUnderstandManyWhiteAndBlackListedExceptionsInOneList() throws Exception {\n\t\tfinal RetryPolicy policy = new RetryPolicy().\n\t\t\t\tretryOn(Exception.class, LinkageError.class).\n\t\t\t\tabortOn(IncompatibleClassChangeError.class, ClassCastException.class, ConnectException.class);\n\n\t\tassertThat(shouldRetryOn(policy, new Exception())).isTrue();\n\t\tassertThat(shouldRetryOn(policy, new IOException())).isTrue();\n\t\tassertThat(shouldRetryOn(policy, new IllegalStateException())).isTrue();\n\t\tassertThat(shouldRetryOn(policy, new NoClassDefFoundError())).isTrue();\n\t\tassertThat(shouldRetryOn(policy, new UnsupportedClassVersionError())).isTrue();\n\n\t\tassertThat(shouldRetryOn(policy, new NoSuchFieldError())).isFalse();\n\t\tassertThat(shouldRetryOn(policy, new OutOfMemoryError())).isFalse();\n\t\tassertThat(shouldRetryOn(policy, new ClassCastException())).isFalse();\n\t\tassertThat(shouldRetryOn(policy, new ConnectException())).isFalse();\n\t}\n\n}\n"
  },
  {
    "path": "src/test/java/com/nurkiewicz/asyncretry/policy/RetryPolicyDefaultsTest.java",
    "content": "package com.nurkiewicz.asyncretry.policy;\n\nimport org.testng.annotations.Test;\n\nimport java.io.IOException;\nimport java.net.SocketException;\nimport java.util.concurrent.TimeoutException;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * @author Tomasz Nurkiewicz\n * @since 7/18/13, 10:56 PM\n */\npublic class RetryPolicyDefaultsTest extends AbstractRetryPolicyTest {\n\n\t@Test\n\tpublic void byDefaultShouldRetryOnAllExceptions() throws Exception {\n\t\tassertThat(shouldRetryOn(new RetryPolicy(), new Exception())).isTrue();\n\t\tassertThat(shouldRetryOn(new RetryPolicy(), new RuntimeException())).isTrue();\n\t\tassertThat(shouldRetryOn(new RetryPolicy(), new IOException())).isTrue();\n\t\tassertThat(shouldRetryOn(new RetryPolicy(), new ClassCastException())).isTrue();\n\t\tassertThat(shouldRetryOn(new RetryPolicy(), new NullPointerException())).isTrue();\n\t\tassertThat(shouldRetryOn(new RetryPolicy(), new IllegalArgumentException())).isTrue();\n\t\tassertThat(shouldRetryOn(new RetryPolicy(), new IllegalStateException())).isTrue();\n\t\tassertThat(shouldRetryOn(new RetryPolicy(), new TimeoutException())).isTrue();\n\t\tassertThat(shouldRetryOn(new RetryPolicy(), new SocketException())).isTrue();\n\t}\n\n\t@Test\n\tpublic void byDefaultShouldRetryOnAllThrowables() throws Exception {\n\t\tassertThat(shouldRetryOn(new RetryPolicy(), new OutOfMemoryError())).isTrue();\n\t\tassertThat(shouldRetryOn(new RetryPolicy(), new StackOverflowError())).isTrue();\n\t\tassertThat(shouldRetryOn(new RetryPolicy(), new NoClassDefFoundError())).isTrue();\n\t}\n}\n\n"
  },
  {
    "path": "src/test/java/com/nurkiewicz/asyncretry/policy/RetryPolicyMaxRetriesTest.java",
    "content": "package com.nurkiewicz.asyncretry.policy;\n\nimport com.nurkiewicz.asyncretry.AbstractBaseTestCase;\nimport org.testng.annotations.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * @author Tomasz Nurkiewicz\n * @since 7/20/13, 7:16 PM\n */\npublic class RetryPolicyMaxRetriesTest extends AbstractBaseTestCase {\n\n\t@Test\n\tpublic void shouldStopAfterConfiguredNumberOfRetries() throws Exception {\n\t\t//given\n\t\tfinal RetryPolicy retryPolicy = new RetryPolicy().withMaxRetries(7);\n\n\t\t//when\n\t\tfinal boolean firstRetry = retryPolicy.shouldContinue(retry(1));\n\t\tfinal boolean lastRetry = retryPolicy.shouldContinue(retry(7));\n\t\tfinal boolean tooManyRetries = retryPolicy.shouldContinue(retry(8));\n\n\t\t//then\n\t\tassertThat(firstRetry).isTrue();\n\t\tassertThat(lastRetry).isTrue();\n\t\tassertThat(tooManyRetries).isFalse();\n\t}\n\n}\n"
  },
  {
    "path": "src/test/java/com/nurkiewicz/asyncretry/policy/RetryPolicyPredicatesTest.java",
    "content": "package com.nurkiewicz.asyncretry.policy;\n\nimport com.nurkiewicz.asyncretry.AsyncRetryContext;\nimport com.nurkiewicz.asyncretry.RetryContext;\nimport org.mockito.Mock;\nimport org.testng.annotations.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.BDDMockito.given;\n\n/**\n * @author Tomasz Nurkiewicz\n * @since 7/20/13, 5:17 PM\n */\npublic class RetryPolicyPredicatesTest extends AbstractRetryPolicyTest {\n\n\t@Mock\n\tprivate RetryContext retryContextMock;\n\n\t@Test\n\tpublic void shouldAbortIfAbortPredicateTrue() throws Exception {\n\t\t//given\n\t\tfinal RetryPolicy retryPolicy = new RetryPolicy().abortIf(t -> true);\n\n\t\t//when\n\t\tfinal boolean shouldRetry = retryPolicy.shouldContinue(retryContextMock);\n\n\t\t//then\n\t\tassertThat(shouldRetry).isFalse();\n\t}\n\n\t@Test\n\tpublic void shouldRetryIfRetryPredicateTrue() throws Exception {\n\t\t//given\n\t\tfinal RetryPolicy retryPolicy = new RetryPolicy().retryIf(t -> true);\n\n\t\t//when\n\t\tfinal boolean shouldRetry = retryPolicy.shouldContinue(retryContextMock);\n\n\t\t//then\n\t\tassertThat(shouldRetry).isTrue();\n\t}\n\n\t@Test\n\tpublic void shouldRetryIfBothPredicatesAbstainButClassShouldRetry() throws Exception {\n\t\t//given\n\t\tfinal RetryPolicy retryPolicy = new RetryPolicy().\n\t\t\t\tretryIf(t -> false).\n\t\t\t\tabortIf(t -> false);\n\t\tgiven(retryContextMock.getLastThrowable()).willReturn(new RuntimeException());\n\n\t\t//when\n\t\tfinal boolean shouldRetry = retryPolicy.shouldContinue(retryContextMock);\n\n\t\t//then\n\t\tassertThat(shouldRetry).isTrue();\n\t}\n\n\t@Test\n\tpublic void shouldAbortIfBothPredicatesAbstainButClassShouldAbort() throws Exception {\n\t\t//given\n\t\tfinal RetryPolicy retryPolicy = new RetryPolicy().\n\t\t\t\tabortOn(NullPointerException.class).\n\t\t\t\tretryIf(t -> false).\n\t\t\t\tabortIf(t -> false);\n\t\tgiven(retryContextMock.getLastThrowable()).willReturn(new NullPointerException());\n\n\t\t//when\n\t\tfinal boolean shouldRetry = retryPolicy.shouldContinue(retryContextMock);\n\n\t\t//then\n\t\tassertThat(shouldRetry).isFalse();\n\t}\n\n\t@Test\n\tpublic void shouldRetryIfPredicateTrueEvenIfClassShouldAbort() throws Exception {\n\t\t//given\n\t\tfinal RetryPolicy retryPolicy = new RetryPolicy().\n\t\t\t\tabortOn(NullPointerException.class).\n\t\t\t\tretryIf(t -> true);\n\t\tgiven(retryContextMock.getLastThrowable()).willReturn(new NullPointerException());\n\n\t\t//when\n\t\tfinal boolean shouldRetry = retryPolicy.shouldContinue(retryContextMock);\n\n\t\t//then\n\t\tassertThat(shouldRetry).isTrue();\n\t}\n\n\t@Test\n\tpublic void shouldAbortIfPredicateTrueEvenIfClassShouldRetry() throws Exception {\n\t\t//given\n\t\tfinal RetryPolicy retryPolicy = new RetryPolicy().\n\t\t\t\tretryOn(NullPointerException.class).\n\t\t\t\tabortIf(t -> true);\n\t\tgiven(retryContextMock.getLastThrowable()).willReturn(new NullPointerException());\n\n\t\t//when\n\t\tfinal boolean shouldRetry = retryPolicy.shouldContinue(retryContextMock);\n\n\t\t//then\n\t\tassertThat(shouldRetry).isFalse();\n\t}\n\n\t@Test\n\tpublic void whenAbortAndRetryPredicatesBothYieldTrueThenAbortWins() throws Exception {\n\t\t//given\n\t\tfinal RetryPolicy retryPolicy = new RetryPolicy().\n\t\t\t\tretryOn(NullPointerException.class).\n\t\t\t\tretryIf(t -> t.getMessage().contains(\"Foo\")).\n\t\t\t\tabortIf(t -> t.getMessage().contains(\"Foo\"));\n\t\tgiven(retryContextMock.getLastThrowable()).willReturn(new NullPointerException(\"Foo\"));\n\n\t\t//when\n\t\tfinal boolean shouldRetry = retryPolicy.shouldContinue(retryContextMock);\n\n\t\t//then\n\t\tassertThat(shouldRetry).isFalse();\n\t}\n\n\t@Test\n\tpublic void shouldProceedIfPredicateFalseAndChildAccepts() throws Exception {\n\t\t//given\n\t\tfinal RetryPolicy retryPolicy = new RetryPolicy().abortIf(t -> false);\n\t\tgiven(retryContextMock.getLastThrowable()).willReturn(new RuntimeException());\n\n\t\t//when\n\t\tfinal boolean shouldRetry = retryPolicy.shouldContinue(retryContextMock);\n\n\t\t//then\n\t\tassertThat(shouldRetry).isTrue();\n\t}\n\n\t@Test\n\tpublic void shouldAbortIfPredicateFalseButShouldNotRetry() throws Exception {\n\t\t//given\n\t\tfinal RetryPolicy retryPolicy = new RetryPolicy().abortIf(t -> false).dontRetry();\n\n\t\t//when\n\t\tfinal boolean shouldRetry = retryPolicy.shouldContinue(retryContextMock);\n\n\t\t//then\n\t\tassertThat(shouldRetry).isFalse();\n\t}\n\n\t@Test\n\tpublic void shouldAbortIfPredicateTrueButShouldNotRetry() throws Exception {\n\t\t//given\n\t\tfinal RetryPolicy retryPolicy = new RetryPolicy().\n\t\t\t\tretryIf(t -> true).\n\t\t\t\tdontRetry();\n\t\tgiven(retryContextMock.getLastThrowable()).willReturn(new NullPointerException());\n\t\tgiven(retryContextMock.getRetryCount()).willReturn(1);\n\n\t\t//when\n\t\tfinal boolean shouldRetry = retryPolicy.shouldContinue(retryContextMock);\n\n\t\t//then\n\t\tassertThat(shouldRetry).isFalse();\n\t}\n\n\t@Test\n\tpublic void shouldExamineExceptionAndDecide() throws Exception {\n\t\t//given\n\t\tfinal RetryPolicy retryPolicy = new RetryPolicy().abortIf(t -> t.getMessage().contains(\"abort\"));\n\n\t\t//when\n\t\tfinal boolean abort = retryPolicy.shouldContinue(new AsyncRetryContext(retryPolicy, 1, new RuntimeException(\"abort\")));\n\t\tfinal boolean retry = retryPolicy.shouldContinue(new AsyncRetryContext(retryPolicy, 1, new RuntimeException(\"normal\")));\n\n\t\t//then\n\t\tassertThat(abort).isFalse();\n\t\tassertThat(retry).isTrue();\n\t}\n\n}\n"
  },
  {
    "path": "src/test/java/com/nurkiewicz/asyncretry/policy/RetryPolicyWhiteListTest.java",
    "content": "package com.nurkiewicz.asyncretry.policy;\n\nimport org.testng.annotations.Test;\n\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.net.ConnectException;\nimport java.net.SocketException;\nimport java.util.concurrent.TimeoutException;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * @author Tomasz Nurkiewicz\n * @since 7/18/13, 11:25 PM\n */\npublic class RetryPolicyWhiteListTest extends AbstractRetryPolicyTest {\n\n\t@Test\n\tpublic void retryOnExceptionExplicitly() throws Exception {\n\t\tfinal RetryPolicy policy = new RetryPolicy().retryOn(Exception.class);\n\n\t\tassertThat(shouldRetryOn(policy, new Exception())).isTrue();\n\t\tassertThat(shouldRetryOn(policy, new RuntimeException())).isTrue();\n\t\tassertThat(shouldRetryOn(policy, new IOException())).isTrue();\n\t\tassertThat(shouldRetryOn(policy, new ClassCastException())).isTrue();\n\t\tassertThat(shouldRetryOn(policy, new NullPointerException())).isTrue();\n\t\tassertThat(shouldRetryOn(policy, new IllegalArgumentException())).isTrue();\n\t\tassertThat(shouldRetryOn(policy, new IllegalStateException())).isTrue();\n\t\tassertThat(shouldRetryOn(policy, new TimeoutException())).isTrue();\n\t\tassertThat(shouldRetryOn(policy, new SocketException())).isTrue();\n\t}\n\n\t@Test\n\tpublic void retryOnExceptionShouldNotRetryOnError() throws Exception {\n\t\tfinal RetryPolicy policy = new RetryPolicy().\n\t\t\t\tretryOn(Exception.class);\n\n\t\tassertThat(shouldRetryOn(policy, new OutOfMemoryError())).isFalse();\n\t\tassertThat(shouldRetryOn(policy, new StackOverflowError())).isFalse();\n\t\tassertThat(shouldRetryOn(policy, new NoClassDefFoundError())).isFalse();\n\t}\n\n\t@Test\n\tpublic void shouldRetryOnOnlyOneSpecificException() throws Exception {\n\t\tfinal RetryPolicy policy = new RetryPolicy().\n\t\t\t\tretryOn(OptimisticLockException.class);\n\n\t\tassertThat(shouldRetryOn(policy, new OptimisticLockException())).isTrue();\n\t}\n\n\t@Test\n\tpublic void shouldNotRetryOnOtherExceptionsIfOneGivenExplicitly() throws Exception {\n\t\tfinal RetryPolicy policy = new RetryPolicy().\n\t\t\t\tretryOn(OptimisticLockException.class);\n\n\t\tassertThat(shouldRetryOn(policy, new Exception())).isFalse();\n\t\tassertThat(shouldRetryOn(policy, new RuntimeException())).isFalse();\n\t\tassertThat(shouldRetryOn(policy, new IOException())).isFalse();\n\t\tassertThat(shouldRetryOn(policy, new ClassCastException())).isFalse();\n\t\tassertThat(shouldRetryOn(policy, new NullPointerException())).isFalse();\n\t\tassertThat(shouldRetryOn(policy, new IllegalArgumentException())).isFalse();\n\t\tassertThat(shouldRetryOn(policy, new IllegalStateException())).isFalse();\n\t\tassertThat(shouldRetryOn(policy, new TimeoutException())).isFalse();\n\t\tassertThat(shouldRetryOn(policy, new SocketException())).isFalse();\n\t}\n\n\t@Test\n\tpublic void shouldNotRetryOnErrorsIfExceptionGivenExplicitly() throws Exception {\n\t\tfinal RetryPolicy policy = new RetryPolicy().\n\t\t\t\tretryOn(OptimisticLockException.class);\n\n\t\tassertThat(shouldRetryOn(policy, new OutOfMemoryError())).isFalse();\n\t\tassertThat(shouldRetryOn(policy, new StackOverflowError())).isFalse();\n\t\tassertThat(shouldRetryOn(policy, new NoClassDefFoundError())).isFalse();\n\t}\n\n\t@Test\n\tpublic void shouldRetryOnAnyOfProvidedExceptions() throws Exception {\n\t\tfinal RetryPolicy policy = new RetryPolicy().\n\t\t\t\tretryOn(OptimisticLockException.class).\n\t\t\t\tretryOn(IOException.class);\n\n\t\tassertThat(shouldRetryOn(policy, new OptimisticLockException())).isTrue();\n\t\tassertThat(shouldRetryOn(policy, new IOException())).isTrue();\n\t}\n\n\t@Test\n\tpublic void shouldRetryOnAnyOfProvidedExceptionsInOneList() throws Exception {\n\t\tfinal RetryPolicy policy = new RetryPolicy().\n\t\t\t\tretryOn(OptimisticLockException.class, IOException.class);\n\n\t\tassertThat(shouldRetryOn(policy, new OptimisticLockException())).isTrue();\n\t\tassertThat(shouldRetryOn(policy, new IOException())).isTrue();\n\t}\n\n\t@Test\n\tpublic void shouldNotRetryOnOtherExceptionsIfFewGivenExplicitly() throws Exception {\n\t\tfinal RetryPolicy policy = new RetryPolicy().\n\t\t\t\tretryOn(OptimisticLockException.class).\n\t\t\t\tretryOn(IOException.class);\n\n\t\tassertThat(shouldRetryOn(policy, new Exception())).isFalse();\n\t\tassertThat(shouldRetryOn(policy, new RuntimeException())).isFalse();\n\t\tassertThat(shouldRetryOn(policy, new ClassCastException())).isFalse();\n\t\tassertThat(shouldRetryOn(policy, new NullPointerException())).isFalse();\n\t\tassertThat(shouldRetryOn(policy, new IllegalArgumentException())).isFalse();\n\t\tassertThat(shouldRetryOn(policy, new IllegalStateException())).isFalse();\n\t\tassertThat(shouldRetryOn(policy, new TimeoutException())).isFalse();\n\t}\n\n\t@Test\n\tpublic void shouldNotRetryOnOtherExceptionsIfFewGivenExplicitlyInOneList() throws Exception {\n\t\tfinal RetryPolicy policy = new RetryPolicy().\n\t\t\t\tretryOn(OptimisticLockException.class, IOException.class);\n\n\t\tassertThat(shouldRetryOn(policy, new Exception())).isFalse();\n\t\tassertThat(shouldRetryOn(policy, new RuntimeException())).isFalse();\n\t\tassertThat(shouldRetryOn(policy, new ClassCastException())).isFalse();\n\t\tassertThat(shouldRetryOn(policy, new NullPointerException())).isFalse();\n\t\tassertThat(shouldRetryOn(policy, new IllegalArgumentException())).isFalse();\n\t\tassertThat(shouldRetryOn(policy, new IllegalStateException())).isFalse();\n\t\tassertThat(shouldRetryOn(policy, new TimeoutException())).isFalse();\n\t}\n\n\t@Test\n\tpublic void shouldNotRetryOnErrorsIfFewExceptionsGivenExplicitly() throws Exception {\n\t\tfinal RetryPolicy policy = new RetryPolicy().\n\t\t\t\tretryOn(OptimisticLockException.class).\n\t\t\t\tretryOn(IOException.class);\n\n\t\tassertThat(shouldRetryOn(policy, new OutOfMemoryError())).isFalse();\n\t\tassertThat(shouldRetryOn(policy, new StackOverflowError())).isFalse();\n\t\tassertThat(shouldRetryOn(policy, new NoClassDefFoundError())).isFalse();\n\t}\n\n\t@Test\n\tpublic void shouldNotRetryOnErrorsIfFewExceptionsGivenExplicitlyInOneList() throws Exception {\n\t\tfinal RetryPolicy policy = new RetryPolicy().\n\t\t\t\tretryOn(OptimisticLockException.class, IOException.class);\n\n\t\tassertThat(shouldRetryOn(policy, new OutOfMemoryError())).isFalse();\n\t\tassertThat(shouldRetryOn(policy, new StackOverflowError())).isFalse();\n\t\tassertThat(shouldRetryOn(policy, new NoClassDefFoundError())).isFalse();\n\t}\n\n\t@Test\n\tpublic void shouldRetryWhenSubclassOfGivenExceptionThrown() throws Exception {\n\t\tfinal RetryPolicy policy = new RetryPolicy().\n\t\t\t\tretryOn(IOException.class);\n\n\t\tassertThat(shouldRetryOn(policy, new FileNotFoundException())).isTrue();\n\t\tassertThat(shouldRetryOn(policy, new SocketException())).isTrue();\n\t\tassertThat(shouldRetryOn(policy, new ConnectException())).isTrue();\n\t}\n\n\t@Test\n\tpublic void shouldNotRetryOnSiblilngExceptions() throws Exception {\n\t\tfinal RetryPolicy policy = new RetryPolicy().\n\t\t\t\tretryOn(FileNotFoundException.class);\n\n\t\tassertThat(shouldRetryOn(policy, new SocketException())).isFalse();\n\t}\n\n\t@Test\n\tpublic void shouldNotRetryOnSuperClassesOfGivenClass() throws Exception {\n\t\tfinal RetryPolicy policy = new RetryPolicy().\n\t\t\t\tretryOn(FileNotFoundException.class);\n\n\t\tassertThat(shouldRetryOn(policy, new IOException())).isFalse();\n\t}\n\n}\n"
  },
  {
    "path": "src/test/resources/logback-test.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<configuration>\n\t<root level=\"ALL\">\n\t\t<appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n\t\t\t<encoder>\n\t\t\t\t<pattern>%d{HH:mm:ss.SSS} | %-5level | %thread | %logger{1} | %m%n%rEx</pattern>\n\t\t\t</encoder>\n\t\t</appender>\n\t</root>\n</configuration>\n"
  }
]