Result enhancements (#215)
* pom: don’t override java version - use LTS - JDK 11 * result: nest tests * Add Result.{from(Either|Optional),ofVoid,ok(),onSuccess,applyOver} * Add Result.flatApplyOver * result: create separate documentation file * Add Result.toEither() * Add Result.onError - by type * Simplify creation of Result in Result.ofVoid * Make Result.ofVoid tolerate exceptions and no return statement * Add missing javadoc for result.VoidCallable * Bump pitest-junit5-plugin from 0.12 to 0.14 (#187) * Bump tiles-maven-plugin from 2.19 to 2.20 (#188) * Bump lombok from 1.18.18 to 1.18.20 (#189) * Bump spotbugs-annotations from 4.2.2 to 4.2.3 (#191) * Bump mockito-junit-jupiter from 3.8.0 to 3.9.0 (#190) * Bump pitest-maven from 1.6.4 to 1.6.5 (#192) * Bump pitest-maven from 1.6.5 to 1.6.6 (#194) * Bump tiles-maven-plugin from 2.20 to 2.21 (#195) * Bump mockito-junit-jupiter from 3.9.0 to 3.10.0 (#196) * Bump junit-bom from 5.7.1 to 5.7.2 (#197) * Bump tiles-maven-plugin from 2.21 to 2.22 (#198) * Bump pitest-maven from 1.6.6 to 1.6.7 (#199) * Bump mockito-junit-jupiter from 3.10.0 to 3.11.0 (#200) * Bump assertj-core from 3.19.0 to 3.20.0 (#202) * Bump mockito-junit-jupiter from 3.11.0 to 3.11.1 (#201) * Bump assertj-core from 3.20.0 to 3.20.1 (#203) * Bump tiles-maven-plugin from 2.22 to 2.23 (#204) * Bump assertj-core from 3.20.1 to 3.20.2 (#205) * Bump mockito-junit-jupiter from 3.11.1 to 3.11.2 (#206) * Upgrade to GitHub-native Dependabot (#193) Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> Co-authored-by: Paul Campbell <pcampbell@kemitix.net> * Create dependabot.yml (#207) * Bump actions/setup-java from 1 to 2.1.0 (#210) * Bump actions/setup-java from 1 to 2.1.0 Bumps [actions/setup-java](https://github.com/actions/setup-java) from 1 to 2.1.0. - [Release notes](https://github.com/actions/setup-java/releases) - [Commits](https://github.com/actions/setup-java/compare/v1...v2.1.0) --- updated-dependencies: - dependency-name: actions/setup-java dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> * Update build-maven.yml * Update deploy-sonatype.yml * Update build-maven.yml * Update build-maven.yml Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Paul Campbell <pcampbell@kemitix.net> * Bump release-drafter/release-drafter from 5.14.0 to 5.15.0 (#209) Bumps [release-drafter/release-drafter](https://github.com/release-drafter/release-drafter) from 5.14.0 to 5.15.0. - [Release notes](https://github.com/release-drafter/release-drafter/releases) - [Commits](https://github.com/release-drafter/release-drafter/compare/v5.14.0...v5.15.0) --- updated-dependencies: - dependency-name: release-drafter/release-drafter dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Paul Campbell <pcampbell@kemitix.net> * Add ResultVoid special Result type - Result now extends ThrowableFunctor allowing mapping functions to throw exceptions - Added ResultVoid to replace the usage of Result<Void> and handle void cases better - Add `flatMapV` and `andThenV` which return ResultVoid * result: Migrate static constructor docs to javadoc * result.Result: move static constructors to top of class * result: add docs and tests for from(Maybe) and from(Either) * pom: add apigaurdian dependency * Remove migrated docs * result: add missing api status * result.Result: group static methods together in file * result.Result: reorder static methods * result.Result: deprecate static maybe methods that don’t include Maybe in name * result.Result.toMaybe: update docs and tests * fix up toMaybe example * result.Result.flatMapMaybe: update docs and tests * result.Result.applyOver(Stream,Consumer): updated docs and tests * result.Result.applyOver: update docs and tests * result.Result: fix bad javadoc encoding * result.Result.flatMapApply: update docs and tests * result.Result.error(Class, Throwable): added * result.Result: suppress warning on long class * result.Result: apply warning supression to entire interface * result.Result.toEither: update docs and tests * result.Result.err: deprecate * [BREAKING] result.Result.err: removed * [BREAKING] result.Result.result: remove method * [BREAKING] result.Result.success: remove method * result.Result.orElseThrow: update docs and tests * result.Result.toVoid: add javadoc and test * result.Result.map: update docs and tests * result.Result.map: add api status * result.Result.isError: update docs and test * result.Result.isError: update docs and test * result.Result.isOkay: update docs and test * result.Result.onError: update docs and test * result.Result.onError: update docs and tests * result.Result: tidy up and add some API statuses * result.Result.orElseThrowUnchecked: update docs and tests * result.Result.orElseThrow: update docs and tests * result.Result.maybe: removed * result.Result.thenWith: update docs and test * result.Result.peek: update docs and tests * result.Result.recover: update docs and tests * result.Result.match: update docs and tests * result.Result.andThen: deprecate in favour of map * result.Result.flatMap: update docs and tests * Update link to documentation for Result * result.Result.flatMapV: add docs and test * result.Result.match: update docs * result.Result.onSuccess: update docs * result.Result.reduce: mark experimental * pom: remove java.version (conflict resolution mistake) * Result: add deprecation annotations * result.Err: remove unused imports * result.BaseResult: extract interface * result.ResultVoid.match: add javadoc * result.ResultVoid: regroup tests and add javadoc for recover and onSuccess * result.ResultVoid.onError: update docs and tests * result.Err.andThen: don't create new object needlessly * result.ResultVoid.andThen: add javadoc and update tests * result.ResultVoid.inject: on error returns new exception, update docs and tests * result.Result.error: suppress false positive unused parameter warning * result.Success.getInstance added * ErrVoid: fix toString * SuccessVoid: break circular dependency * Err: break circular dependency * Result.result: added * Success: break cyclic dependency * Result.result: add javadoc and test * Result.result: reuse static of(…) * github/dependabot: clean up * github/workflow: remove unused graphviz * github/workflow: drop java support Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This commit is contained in:
parent
a885474ff0
commit
571fd2641a
21 changed files with 3428 additions and 1167 deletions
6
.github/dependabot.yml
vendored
6
.github/dependabot.yml
vendored
|
@ -4,12 +4,6 @@ updates:
|
||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: daily
|
interval: daily
|
||||||
open-pull-requests-limit: 10
|
|
||||||
ignore:
|
|
||||||
- dependency-name: com.github.spotbugs:spotbugs-annotations
|
|
||||||
versions:
|
|
||||||
- 4.2.0
|
|
||||||
- 4.2.1
|
|
||||||
- package-ecosystem: "github-actions"
|
- package-ecosystem: "github-actions"
|
||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
|
|
3
.github/workflows/build-maven.yml
vendored
3
.github/workflows/build-maven.yml
vendored
|
@ -11,9 +11,8 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
java: [ 8, 11, 16 ]
|
java: [ 11, 16 ]
|
||||||
steps:
|
steps:
|
||||||
- uses: kamiazya/setup-graphviz@v1
|
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: setup-jdk-${{ matrix.java }}
|
- name: setup-jdk-${{ matrix.java }}
|
||||||
uses: actions/setup-java@v2.1.0
|
uses: actions/setup-java@v2.1.0
|
||||||
|
|
3
.github/workflows/deploy-sonatype.yml
vendored
3
.github/workflows/deploy-sonatype.yml
vendored
|
@ -10,13 +10,12 @@ jobs:
|
||||||
deploy:
|
deploy:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: kamiazya/setup-graphviz@v1
|
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up JDK
|
- name: Set up JDK
|
||||||
uses: actions/setup-java@v2.1.0
|
uses: actions/setup-java@v2.1.0
|
||||||
with:
|
with:
|
||||||
distribution: 'adopt'
|
distribution: 'adopt'
|
||||||
java-version: 8
|
java-version: 11
|
||||||
- name: Build with Maven
|
- name: Build with Maven
|
||||||
run: mvn -B install
|
run: mvn -B install
|
||||||
- name: Nexus Repo Publish
|
- name: Nexus Repo Publish
|
||||||
|
|
287
README.md
287
README.md
|
@ -18,7 +18,7 @@ https://search.maven.org/artifact/net.kemitix/mon)
|
||||||
- [Wrapper](#Wrapper) - light-weight type-alias-like
|
- [Wrapper](#Wrapper) - light-weight type-alias-like
|
||||||
- [TypeAlias](#TypeAlias) - type-alias-like monadic wrapper
|
- [TypeAlias](#TypeAlias) - type-alias-like monadic wrapper
|
||||||
- [Maybe](#Maybe) - Maybe, Just or Nothing
|
- [Maybe](#Maybe) - Maybe, Just or Nothing
|
||||||
- [Result](#Result) - Result, Success or Err
|
- [Result](https://kemitix.github.io/mon/net/kemitix/mon/result/package-summary.html) - Result, Success or Err
|
||||||
- [Tree](#Tree) - generic trees
|
- [Tree](#Tree) - generic trees
|
||||||
- [Lazy](#Lazy) - lazy evaluation
|
- [Lazy](#Lazy) - lazy evaluation
|
||||||
- [Either](#Either) - Either, Left or Right
|
- [Either](#Either) - Either, Left or Right
|
||||||
|
@ -402,292 +402,7 @@ Optional<Integer> optional = Maybe.maybe(getValue())
|
||||||
.toOptional();
|
.toOptional();
|
||||||
```
|
```
|
||||||
---
|
---
|
||||||
## Result
|
|
||||||
|
|
||||||
Allows handling error conditions without the need to `catch` exceptions.
|
|
||||||
|
|
||||||
When a `Result` is returned from a method, it will contain one of two values.
|
|
||||||
Either the actual result, or an error in the form of an `Exception`. The
|
|
||||||
exception is returned within the `Result` and is not thrown.
|
|
||||||
|
|
||||||
`Result` is a Monad.
|
|
||||||
|
|
||||||
### Example
|
|
||||||
|
|
||||||
``` java
|
|
||||||
import net.kemitix.mon.result.Result;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
class ResultExample implements Runnable {
|
|
||||||
|
|
||||||
public static void main(final String[] args) {
|
|
||||||
new ResultExample().run();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
Result.of(() -> callRiskyMethod())
|
|
||||||
.flatMap(state -> doSomething(state))
|
|
||||||
.match(
|
|
||||||
success -> System.out.println(success),
|
|
||||||
error -> error.printStackTrace()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String callRiskyMethod() throws IOException {
|
|
||||||
return "I'm fine";
|
|
||||||
}
|
|
||||||
|
|
||||||
private Result<String> doSomething(final String state) {
|
|
||||||
return Result.of(() -> state + ", it's all good.");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
In the above example the string `"I'm fine"` is returned by
|
|
||||||
`callRiskyMethod()` within a successful `Result`. The `.flatMap()` call,
|
|
||||||
unwraps that `Result` and, as it is a success, passes the contents to
|
|
||||||
`doSomething()`, which in turn returns a `Result` that the `.flatMap()` call
|
|
||||||
returns. `match()` is called on the `Result` and, being a success, will call
|
|
||||||
the success `Consumer`.
|
|
||||||
|
|
||||||
Had `callRiskyMethod()` thrown an exception it would have been caught by the
|
|
||||||
`Result.of()` method, which would then return an error `Result`. An error
|
|
||||||
`Result` would skip the `flatMap` and continue at the `match()` where it
|
|
||||||
would have called the error `Consumer`.
|
|
||||||
|
|
||||||
### Static Constructors
|
|
||||||
|
|
||||||
#### `static <T> Result<T> of(Callable<T> callable)`
|
|
||||||
|
|
||||||
Create a `Result` for the output of the `Callable`.
|
|
||||||
|
|
||||||
If the `Callable` throws an `Exception`, then the `Result` will be an error and
|
|
||||||
will contain that exception.
|
|
||||||
|
|
||||||
This will be the main starting point for most `Result`s where the callable
|
|
||||||
could throw an `Exception`.
|
|
||||||
|
|
||||||
``` java
|
|
||||||
Result<Integer> okay = Result.of(() -> 1);
|
|
||||||
Result<Integer> error = Result.of(() -> {throw new RuntimeException();});
|
|
||||||
```
|
|
||||||
---
|
|
||||||
#### `static <T> Result<T> ok(T value)`
|
|
||||||
|
|
||||||
Create a `Result` for a success.
|
|
||||||
|
|
||||||
Use this where you have a value that you want to place into the `Result` context.
|
|
||||||
|
|
||||||
``` java
|
|
||||||
Result<Integer> okay = Result.ok(1);
|
|
||||||
```
|
|
||||||
---
|
|
||||||
#### `static <T> Result<T> error(Throwable error)`
|
|
||||||
|
|
||||||
Create a `Result` for an error.
|
|
||||||
|
|
||||||
``` java
|
|
||||||
Result<Integer> error = Result.error(new RuntimeException());
|
|
||||||
```
|
|
||||||
---
|
|
||||||
### Static Methods
|
|
||||||
|
|
||||||
These static methods provide integration with the `Maybe` class.
|
|
||||||
|
|
||||||
#### `static <T> Maybe<T> toMaybe(Result<T> result)`
|
|
||||||
|
|
||||||
Creates a `Maybe` from the `Result`, where the `Result` is a success, then
|
|
||||||
the `Maybe` will be a `Just` contain the value of the `Result`. However, if the
|
|
||||||
`Result` is an error, then the `Maybe` will be `Nothing`.
|
|
||||||
|
|
||||||
``` java
|
|
||||||
Result<Integer> result = Result.of(() -> getValue());
|
|
||||||
Maybe<Integer> maybe = Result.toMaybe(result);
|
|
||||||
```
|
|
||||||
---
|
|
||||||
#### `static <T> Result<T> fromMaybe(Maybe<T> maybe, Supplier<Throwable> error)`
|
|
||||||
|
|
||||||
Creates a `Result` from the `Maybe`, where the `Result` will be an error
|
|
||||||
if the `Maybe` is nothing. Where the `Maybe` is nothing, then the
|
|
||||||
`Supplier<Throwable>` will provide the error for the `Result`.
|
|
||||||
|
|
||||||
``` java
|
|
||||||
Maybe<Integer> maybe = Maybe.maybe(getValue());
|
|
||||||
Result<Integer> result = Result.fromMaybe(maybe,
|
|
||||||
() -> new NoSuchFileException("filename"));
|
|
||||||
```
|
|
||||||
---
|
|
||||||
#### `static <T> Result<Maybe<T>> invert(Maybe<Result<T>> maybeResult)`
|
|
||||||
|
|
||||||
Swaps the `Result` within a `Maybe`, so that `Result` contains a `Maybe`.
|
|
||||||
|
|
||||||
``` java
|
|
||||||
Maybe<Result<Integer>> maybe = Maybe.maybe(Result.of(() -> getValue()));
|
|
||||||
Result<Maybe<Integer>> result = Result.invert(maybe);
|
|
||||||
```
|
|
||||||
---
|
|
||||||
#### `static <T,R> Result<Maybe<R>> flatMapMaybe(Result<Maybe<T>> maybeResult, Function<Maybe<T>,Result<Maybe<R>>> f)`
|
|
||||||
|
|
||||||
Applies the function to the contents of a `Maybe` within the `Result`.
|
|
||||||
|
|
||||||
``` java
|
|
||||||
Result<Maybe<Integer>> result = Result.of(() -> Maybe.maybe(getValue()));
|
|
||||||
Result<Maybe<Integer>> maybeResult = Result.flatMapMaybe(result,
|
|
||||||
maybe -> Result.of(() -> maybe.map(v -> v * 2)));
|
|
||||||
```
|
|
||||||
---
|
|
||||||
### Instance Methods
|
|
||||||
|
|
||||||
#### `<R> Result<R> map(Function<T,R> f)`
|
|
||||||
|
|
||||||
If the `Result` is a success, then apply the function to the value within the
|
|
||||||
`Result`, returning the result within another `Result`. If the `Result` is an
|
|
||||||
error, then return the error.
|
|
||||||
|
|
||||||
``` java
|
|
||||||
Result<String> result = Result.of(() -> getValue())
|
|
||||||
.map(v -> String.valueOf(v));
|
|
||||||
```
|
|
||||||
---
|
|
||||||
#### `<R> Result<R> flatMap(Function<T,Result<R>> f)`
|
|
||||||
|
|
||||||
If the `Result` is a success, then return a new `Result` containing the result
|
|
||||||
of applying the function to the contents of the `Result`. If the `Result` is an
|
|
||||||
error, then return the error.
|
|
||||||
|
|
||||||
``` java
|
|
||||||
Result<String> result =
|
|
||||||
Result.of(() -> getValue())
|
|
||||||
.flatMap(v -> Result.of(() -> String.valueOf(v)));
|
|
||||||
```
|
|
||||||
---
|
|
||||||
#### `<R> Result<R> andThen(Function<T,Callable<R>> f)`
|
|
||||||
|
|
||||||
Maps a successful `Result` to another `Result` using a `Callable` that is able
|
|
||||||
to throw a checked exception.
|
|
||||||
|
|
||||||
``` java
|
|
||||||
Result<String> result =
|
|
||||||
Result.of(() -> getValue())
|
|
||||||
.andThen(v -> () -> {throw new IOException();});
|
|
||||||
```
|
|
||||||
---
|
|
||||||
#### `void match(Consumer<T> onSuccess, Consumer<Throwable> onError)`
|
|
||||||
|
|
||||||
Matches the `Result`, either success or error, and supplies the appropriate
|
|
||||||
`Consumer` with the value or error.
|
|
||||||
|
|
||||||
``` java
|
|
||||||
Result.of(() -> getValue())
|
|
||||||
.match(
|
|
||||||
success -> System.out.println(success),
|
|
||||||
error -> System.err.println(error.getMessage())
|
|
||||||
);
|
|
||||||
```
|
|
||||||
---
|
|
||||||
#### `Result<T> recover(Function<Throwable,Result<T>> f)`
|
|
||||||
|
|
||||||
Provide a way to attempt to recover from an error state.
|
|
||||||
|
|
||||||
``` java
|
|
||||||
Result<Integer> result = Result.of(() -> getValue())
|
|
||||||
.recover(e -> Result.of(() -> getSafeValue(e)));
|
|
||||||
```
|
|
||||||
---
|
|
||||||
#### `Result<T> peek(Consumer<T> consumer)`
|
|
||||||
|
|
||||||
Provide the value within the Result, if it is a success, to the `Consumer`,
|
|
||||||
and returns this Result.
|
|
||||||
|
|
||||||
``` java
|
|
||||||
Result<Integer> result = Result.of(() -> getValue())
|
|
||||||
.peek(v -> System.out.println(v));
|
|
||||||
```
|
|
||||||
---
|
|
||||||
#### `Result<T> thenWith(Function<T,WithResultContinuation<T>> f)`
|
|
||||||
|
|
||||||
Perform the continuation with the current `Result` value then return the
|
|
||||||
current `Result`, assuming there was no error in the continuation.
|
|
||||||
|
|
||||||
``` java
|
|
||||||
Result<Integer> result =
|
|
||||||
Result.of(() -> getValue())
|
|
||||||
.thenWith(v -> () -> System.out.println(v))
|
|
||||||
.thenWith(v -> () -> {throw new IOException();});
|
|
||||||
```
|
|
||||||
---
|
|
||||||
#### `Result<Maybe<T>> maybe(Predicate<T> predicate)`
|
|
||||||
|
|
||||||
Wraps the value within the `Result` in a `Maybe`, either a `Just` if the
|
|
||||||
predicate is true, or `Nothing`.
|
|
||||||
|
|
||||||
``` java
|
|
||||||
Result<Maybe<Integer>> result = Result.of(() -> getValue())
|
|
||||||
.maybe(v -> v % 2 == 0);
|
|
||||||
```
|
|
||||||
---
|
|
||||||
#### `T orElseThrow()`
|
|
||||||
|
|
||||||
Extracts the successful value from the `Result`, or throws the error
|
|
||||||
within a `CheckedErrorResultException`.
|
|
||||||
|
|
||||||
``` java
|
|
||||||
Integer result = Result.of(() -> getValue())
|
|
||||||
.orElseThrow();
|
|
||||||
```
|
|
||||||
---
|
|
||||||
#### `<E extends Exception> T orElseThrow(Class<E> type) throws E`
|
|
||||||
|
|
||||||
Extracts the successful value from the `Result`, or throws the error when it
|
|
||||||
is of the given type. Any other errors will be thrown inside an
|
|
||||||
`UnexpectedErrorResultException`.
|
|
||||||
|
|
||||||
``` java
|
|
||||||
Integer result = Result.of(() -> getValue())
|
|
||||||
.orElseThrow(IOException.class);
|
|
||||||
```
|
|
||||||
---
|
|
||||||
#### `T orElseThrowUnchecked()`
|
|
||||||
|
|
||||||
Extracts the successful value from the `Result`, or throws the error within
|
|
||||||
an `ErrorResultException`.
|
|
||||||
|
|
||||||
``` java
|
|
||||||
Integer result = Result.of(() -> getValue())
|
|
||||||
.orElseThrowUnchecked();
|
|
||||||
```
|
|
||||||
---
|
|
||||||
#### `void onError(Consumer<Throwable> errorConsumer)`
|
|
||||||
|
|
||||||
A handler for error states. If the `Result` is an error, then supply the error
|
|
||||||
to the `Consumer`. Does nothing if the `Result` is a success.
|
|
||||||
|
|
||||||
``` java
|
|
||||||
Result.of(() -> getValue())
|
|
||||||
.onError(e -> handleError(e));
|
|
||||||
```
|
|
||||||
---
|
|
||||||
#### `boolean isOkay()`
|
|
||||||
|
|
||||||
Checks if the `Result` is a success.
|
|
||||||
|
|
||||||
``` java
|
|
||||||
boolean isOkay = Result.of(() -> getValue())
|
|
||||||
.isOkay();
|
|
||||||
```
|
|
||||||
---
|
|
||||||
#### `boolean isError()`
|
|
||||||
|
|
||||||
Checks if the `Result` is an error.
|
|
||||||
|
|
||||||
``` java
|
|
||||||
boolean isError = Result.of(() -> getValue())
|
|
||||||
.isError();
|
|
||||||
```
|
|
||||||
---
|
|
||||||
## Tree
|
## Tree
|
||||||
|
|
||||||
A Generalised tree, where each node may or may not have an item, and may have
|
A Generalised tree, where each node may or may not have an item, and may have
|
||||||
|
|
8
pom.xml
8
pom.xml
|
@ -33,7 +33,6 @@
|
||||||
<inceptionYear>2017</inceptionYear>
|
<inceptionYear>2017</inceptionYear>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<java.version>1.8</java.version>
|
|
||||||
<junit.version>5.7.2</junit.version>
|
<junit.version>5.7.2</junit.version>
|
||||||
<mockito.version>3.11.2</mockito.version>
|
<mockito.version>3.11.2</mockito.version>
|
||||||
<assertj.version>3.20.2</assertj.version>
|
<assertj.version>3.20.2</assertj.version>
|
||||||
|
@ -45,6 +44,7 @@
|
||||||
<pitest-maven-plugin.version>1.6.8</pitest-maven-plugin.version>
|
<pitest-maven-plugin.version>1.6.8</pitest-maven-plugin.version>
|
||||||
<pitest-junit5-plugin.version>0.14</pitest-junit5-plugin.version>
|
<pitest-junit5-plugin.version>0.14</pitest-junit5-plugin.version>
|
||||||
<spotbugs.version>4.2.3</spotbugs.version>
|
<spotbugs.version>4.2.3</spotbugs.version>
|
||||||
|
<apiguardian-api.version>1.1.1</apiguardian-api.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
@ -55,6 +55,12 @@
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apiguardian</groupId>
|
||||||
|
<artifactId>apiguardian-api</artifactId>
|
||||||
|
<version>${apiguardian-api.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.junit.jupiter</groupId>
|
<groupId>org.junit.jupiter</groupId>
|
||||||
<artifactId>junit-jupiter-api</artifactId>
|
<artifactId>junit-jupiter-api</artifactId>
|
||||||
|
|
36
src/main/java/net/kemitix/mon/ThrowableFunctor.java
Normal file
36
src/main/java/net/kemitix/mon/ThrowableFunctor.java
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
package net.kemitix.mon;
|
||||||
|
|
||||||
|
import net.kemitix.mon.result.ThrowableFunction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ThrowableFunctor is used for types that can be mapped over.
|
||||||
|
*
|
||||||
|
* <p>A ThrowableFunctor is identical to a normal Functor except that the
|
||||||
|
* map method may throw an exception.</p>
|
||||||
|
*
|
||||||
|
* <p>Implementations of ThrowableFunctor should satisfy the following laws:</p>
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>map id == id</li>
|
||||||
|
* <li>map (f . g) == map f . map g</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @param <T> the type of the Functor
|
||||||
|
* @param <F> the type of the mapped Functor
|
||||||
|
*
|
||||||
|
* @author Paul Campbell (pcampbell@kemitix.net)
|
||||||
|
*/
|
||||||
|
public interface ThrowableFunctor<T, F extends ThrowableFunctor<?, ?>> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies the function to the value within the ThrowableFunctor, returning
|
||||||
|
* the result within another ThrowableFunctor.
|
||||||
|
*
|
||||||
|
* @param f the function to apply
|
||||||
|
* @param <R> the type of the content of the mapped functor
|
||||||
|
*
|
||||||
|
* @return a ThrowableFunctor containing the result of the function
|
||||||
|
* {@code f} applied to the value
|
||||||
|
*/
|
||||||
|
<R> F map(ThrowableFunction<T, R, ?> f);
|
||||||
|
}
|
31
src/main/java/net/kemitix/mon/TypeReference.java
Normal file
31
src/main/java/net/kemitix/mon/TypeReference.java
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
package net.kemitix.mon;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class to capture a reference to a type.
|
||||||
|
*
|
||||||
|
* <p>Usually to be used when passing a type as a parameter to method.</p>
|
||||||
|
*
|
||||||
|
* <pre><code>
|
||||||
|
* TypeReference<Integer> ref1 = TypeReference.create();
|
||||||
|
* var ref2 = TypeReference.<Integer>create();
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* @param <T> the type being references
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("PMD.ClassNamingConventions")
|
||||||
|
final public class TypeReference<T> {
|
||||||
|
|
||||||
|
private TypeReference() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance of a TypeReference.
|
||||||
|
*
|
||||||
|
* @param <R> the type being references.
|
||||||
|
* @return the TypeReference
|
||||||
|
*/
|
||||||
|
public static <R> TypeReference<R> create() {
|
||||||
|
return new TypeReference<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -94,6 +94,18 @@ public interface Maybe<T> extends Functor<T, Maybe<?>> {
|
||||||
.orElseGet(Maybe::nothing);
|
.orElseGet(Maybe::nothing);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a Maybe from an Optional.
|
||||||
|
*
|
||||||
|
* @param optional the Optional
|
||||||
|
* @param <T> the type of the value
|
||||||
|
* @return a Just if the Optional contains a value, otherwise a Nothing
|
||||||
|
*/
|
||||||
|
static <T> Maybe<T> fromOptional(Optional<T> optional) {
|
||||||
|
return optional.map(Maybe::maybe)
|
||||||
|
.orElseGet(Maybe::nothing);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the Maybe is a Just.
|
* Checks if the Maybe is a Just.
|
||||||
*
|
*
|
||||||
|
|
61
src/main/java/net/kemitix/mon/result/BaseResult.java
Normal file
61
src/main/java/net/kemitix/mon/result/BaseResult.java
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
package net.kemitix.mon.result;
|
||||||
|
|
||||||
|
import org.apiguardian.api.API;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import static org.apiguardian.api.API.Status.STABLE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base interface for {@link Result} and {@link ResultVoid}.
|
||||||
|
*/
|
||||||
|
public interface BaseResult {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the Result is an error.
|
||||||
|
*
|
||||||
|
* <pre><code>
|
||||||
|
* boolean isError = Result.of(() -> getValue())
|
||||||
|
* .isError();
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* @return true if the Result is an error.
|
||||||
|
*/
|
||||||
|
@API(status = STABLE)
|
||||||
|
boolean isError();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the Result is a success.
|
||||||
|
*
|
||||||
|
* <pre><code>
|
||||||
|
* boolean isOkay = Result.of(() -> getValue())
|
||||||
|
* .isOkay();
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* @return true if the Result is a success.
|
||||||
|
*/
|
||||||
|
@API(status = STABLE)
|
||||||
|
boolean isOkay();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A handler for error states.
|
||||||
|
*
|
||||||
|
* <p>If the {@code Result} is an error, then supply the error
|
||||||
|
* to the {@code Consumer}. Does nothing if the {@code Result} is a
|
||||||
|
* success.</p>
|
||||||
|
*
|
||||||
|
* <p>When this is an error then tne Consumer will be supplied with the
|
||||||
|
* error. When this is a success, then nothing happens.</p>
|
||||||
|
*
|
||||||
|
* <pre><code>
|
||||||
|
* void handleError(Throwable e) {...}
|
||||||
|
* Result.of(() -> doSomething())
|
||||||
|
* .onError(e -> handleError(e));
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* @param errorConsumer the consumer to handle the error
|
||||||
|
*/
|
||||||
|
@API(status = STABLE)
|
||||||
|
void onError(Consumer<Throwable> errorConsumer);
|
||||||
|
|
||||||
|
}
|
|
@ -22,14 +22,12 @@
|
||||||
package net.kemitix.mon.result;
|
package net.kemitix.mon.result;
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import net.kemitix.mon.maybe.Maybe;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
import java.util.function.BinaryOperator;
|
import java.util.function.BinaryOperator;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Predicate;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An Error Result.
|
* An Error Result.
|
||||||
|
@ -37,7 +35,8 @@ import java.util.function.Predicate;
|
||||||
* @param <T> the type of the value in the Result if it has been a success
|
* @param <T> the type of the value in the Result if it has been a success
|
||||||
*/
|
*/
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@SuppressWarnings({"methodcount", "PMD.CyclomaticComplexity"})
|
@SuppressWarnings({"methodcount", "PMD.TooManyMethods", "PMD.ExcessivePublicCount",
|
||||||
|
"PMD.CyclomaticComplexity"})
|
||||||
class Err<T> implements Result<T> {
|
class Err<T> implements Result<T> {
|
||||||
|
|
||||||
private final Throwable error;
|
private final Throwable error;
|
||||||
|
@ -54,12 +53,17 @@ class Err<T> implements Result<T> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <R> Result<R> flatMap(final Function<T, Result<R>> f) {
|
public <R> Result<R> flatMap(final Function<T, Result<R>> f) {
|
||||||
return err(error);
|
return new Err<>(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <R> Result<R> map(final Function<T, R> f) {
|
public ResultVoid flatMapV(final Function<T, ResultVoid> f) {
|
||||||
return err(error);
|
return new ErrVoid(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <R> Result<R> map(final ThrowableFunction<T, R, ?> f) {
|
||||||
|
return new Err<>(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -67,18 +71,13 @@ class Err<T> implements Result<T> {
|
||||||
onError.accept(error);
|
onError.accept(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Result<Maybe<T>> maybe(final Predicate<T> predicate) {
|
|
||||||
return err(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public T orElseThrow() throws CheckedErrorResultException {
|
public T orElseThrow() throws CheckedErrorResultException {
|
||||||
throw CheckedErrorResultException.with(error);
|
throw CheckedErrorResultException.with(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings({"unchecked", "PMD.AvoidDuplicateLiterals"})
|
||||||
public <E extends Exception> T orElseThrow(final Class<E> type) throws E {
|
public <E extends Exception> T orElseThrow(final Class<E> type) throws E {
|
||||||
if (type.isInstance(error)) {
|
if (type.isInstance(error)) {
|
||||||
throw (E) error;
|
throw (E) error;
|
||||||
|
@ -101,14 +100,31 @@ class Err<T> implements Result<T> {
|
||||||
return f.apply(error);
|
return f.apply(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(final Consumer<T> successConsumer) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(final Consumer<Throwable> errorConsumer) {
|
public void onError(final Consumer<Throwable> errorConsumer) {
|
||||||
errorConsumer.accept(error);
|
errorConsumer.accept(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <E extends Throwable> Result<T> onError(
|
||||||
|
final Class<E> errorClass,
|
||||||
|
final Consumer<E> consumer
|
||||||
|
) {
|
||||||
|
if (error.getClass().isAssignableFrom(errorClass)) {
|
||||||
|
consumer.accept((E) error);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <R> Result<R> andThen(final Function<T, Callable<R>> f) {
|
public <R> Result<R> andThen(final Function<T, Callable<R>> f) {
|
||||||
return err(error);
|
return (Result<R>) this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -116,6 +132,11 @@ class Err<T> implements Result<T> {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResultVoid thenWithV(final Function<T, WithResultContinuation<T>> f) {
|
||||||
|
return toVoid();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Result<T> reduce(final Result<T> identify, final BinaryOperator<T> operator) {
|
public Result<T> reduce(final Result<T> identify, final BinaryOperator<T> operator) {
|
||||||
return this;
|
return this;
|
||||||
|
@ -123,7 +144,8 @@ class Err<T> implements Result<T> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(final Object other) {
|
public boolean equals(final Object other) {
|
||||||
return other instanceof Err && Objects.equals(error, ((Err) other).error);
|
return other instanceof Err
|
||||||
|
&& Objects.equals(error, ((Err) other).error);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -135,4 +157,9 @@ class Err<T> implements Result<T> {
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return String.format("Result.Error{error=%s}", error);
|
return String.format("Result.Error{error=%s}", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResultVoid toVoid() {
|
||||||
|
return new ErrVoid(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
79
src/main/java/net/kemitix/mon/result/ErrVoid.java
Normal file
79
src/main/java/net/kemitix/mon/result/ErrVoid.java
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
package net.kemitix.mon.result;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
public class ErrVoid implements ResultVoid {
|
||||||
|
|
||||||
|
private final Throwable error;
|
||||||
|
|
||||||
|
ErrVoid(final Throwable error) {
|
||||||
|
this.error = error;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isError() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isOkay() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void match(
|
||||||
|
final Runnable onSuccess,
|
||||||
|
final Consumer<Throwable> onError
|
||||||
|
) {
|
||||||
|
onError.accept(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResultVoid recover(final Function<Throwable, ResultVoid> f) {
|
||||||
|
return f.apply(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(final Runnable runnable) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(final Consumer<Throwable> errorConsumer) {
|
||||||
|
errorConsumer.accept(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <E extends Throwable> ResultVoid onError(
|
||||||
|
final Class<E> errorClass,
|
||||||
|
final Consumer<E> consumer
|
||||||
|
) {
|
||||||
|
if (error.getClass().isAssignableFrom(errorClass)) {
|
||||||
|
consumer.accept((E) error);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResultVoid andThen(final VoidCallable f) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("Result.ErrVoid{error=%s}", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object other) {
|
||||||
|
return other instanceof ErrVoid && Objects.equals(error, ((ErrVoid) other).error);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -21,178 +21,416 @@
|
||||||
|
|
||||||
package net.kemitix.mon.result;
|
package net.kemitix.mon.result;
|
||||||
|
|
||||||
import net.kemitix.mon.Functor;
|
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
||||||
|
import net.kemitix.mon.ThrowableFunctor;
|
||||||
|
import net.kemitix.mon.TypeReference;
|
||||||
|
import net.kemitix.mon.experimental.either.Either;
|
||||||
import net.kemitix.mon.maybe.Maybe;
|
import net.kemitix.mon.maybe.Maybe;
|
||||||
|
import org.apiguardian.api.API;
|
||||||
|
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.function.*;
|
import java.util.function.*;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static org.apiguardian.api.API.Status.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An Either type for holding a result or an error (Throwable).
|
* A type for holding a <strong>result</strong> or an <strong>error</strong>.
|
||||||
|
*
|
||||||
|
* <h2>Static Constructors:</h2>
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link #ok()}</li>
|
||||||
|
* <li>{@link #ok(Object)}</li>
|
||||||
|
* <li>{@link #of(Callable)}</li>
|
||||||
|
* <li>{@link #ofVoid(VoidCallable)}</li>
|
||||||
|
* <li>{@link #error(Throwable)}</li>
|
||||||
|
* <li>{@link #error(TypeReference, Throwable)}</li>
|
||||||
|
* <li>{@link #from(Either)}</li>
|
||||||
|
* <li>{@link #from(Maybe, Supplier)}</li>
|
||||||
|
* </ul>
|
||||||
*
|
*
|
||||||
* @param <T> the type of the result when a success
|
* @param <T> the type of the result when a success
|
||||||
* @author Paul Campbell (pcampbell@kemitix.net)
|
* @author Paul Campbell (pcampbell@kemitix.net)
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings({"methodcount", "PMD.TooManyMethods"})
|
@SuppressWarnings({"methodcount", "PMD.TooManyMethods", "PMD.ExcessivePublicCount", "PMD.ExcessiveClassLength",
|
||||||
public interface Result<T> extends Functor<T, Result<?>> {
|
"PMD.AvoidCatchingThrowable"})
|
||||||
|
public interface Result<T> extends BaseResult, ThrowableFunctor<T, ThrowableFunctor<?, ?>> {
|
||||||
|
|
||||||
|
// BEGIN Static Constructors
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a Result from the Maybe, where the Result will be an error if the Maybe is Nothing.
|
* Creates a success Result with no value.
|
||||||
*
|
*
|
||||||
* @param maybe the Maybe the might contain the value of the Result
|
* <pre><code>
|
||||||
* @param error the error that will be the Result if maybe is Nothing
|
* ResultVoid okay = Result.ok();
|
||||||
* @param <T> the type of the Maybe and the Result
|
* </code></pre>
|
||||||
* @return a Result containing the value of the Maybe when it is a Just, or the error when it is Nothing
|
* @return a successful Result
|
||||||
*/
|
*/
|
||||||
static <T> Result<T> fromMaybe(final Maybe<T> maybe, final Supplier<Throwable> error) {
|
@API(status = STABLE)
|
||||||
return maybe.map(Result::ok)
|
static ResultVoid ok() {
|
||||||
.orElseGet(() -> Result.error(error.get()));
|
return SuccessVoid.getInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a Result for an error.
|
* Create a success Result with a value.
|
||||||
*
|
*
|
||||||
* @param error the error (Throwable)
|
* <pre><code>
|
||||||
* @param <T> the type had the result been a success
|
* Result<Integer> okay = Result.ok(1);
|
||||||
* @return an error Result
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* @param value the value
|
||||||
|
* @param <R> the type of the value
|
||||||
|
* @return a successful Result
|
||||||
*/
|
*/
|
||||||
default <T> Result<T> err(final Throwable error) {
|
@API(status = STABLE)
|
||||||
return new Err<>(error);
|
static <R> Result<R> ok(final R value) {
|
||||||
|
return new Success<>(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a Result for an error.
|
* Create a {@link Result} for the output of the {@link Callable}.
|
||||||
*
|
*
|
||||||
* @param error the error (Throwable)
|
* <p>If the {@code Callable} succeeds then the {@code Result} will be a
|
||||||
* @param <T> the type had the result been a success
|
* {@link Success} and will contain the value.
|
||||||
* @return an error Result
|
* If it throws an {@code Exception}, then the {@code Result} will be an
|
||||||
*/
|
* {@link Err} and will contain that exception.</p>
|
||||||
static <T> Result<T> error(final Throwable error) {
|
*
|
||||||
return new Err<>(error);
|
* <pre><code>
|
||||||
}
|
* Result<Integer$gt; okay = Result.of(() -> 1);
|
||||||
|
* Result<Integer> error = Result.of(() -> {
|
||||||
/**
|
* throw new RuntimeException();
|
||||||
* Create a Result for a output of the Callable.
|
* });
|
||||||
|
* </code></pre>
|
||||||
*
|
*
|
||||||
* @param callable the callable to produce the result
|
* @param callable the callable to produce the result
|
||||||
* @param <T> the type of the value
|
* @param <T> the type of the value
|
||||||
* @return a Result
|
* @return a Result
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings({"illegalcatch", "PMD.AvoidCatchingThrowable", "PMD.AvoidDuplicateLiterals"})
|
@API(status = STABLE)
|
||||||
default <T> Result<T> result(final Callable<T> callable) {
|
|
||||||
try {
|
|
||||||
return Result.ok(callable.call());
|
|
||||||
} catch (final Throwable e) {
|
|
||||||
return Result.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a Result for a output of the Callable.
|
|
||||||
*
|
|
||||||
* @param callable the callable to produce the result
|
|
||||||
* @param <T> the type of the value
|
|
||||||
* @return a Result
|
|
||||||
*/
|
|
||||||
@SuppressWarnings({"illegalcatch", "PMD.AvoidCatchingThrowable"})
|
|
||||||
static <T> Result<T> of(final Callable<T> callable) {
|
static <T> Result<T> of(final Callable<T> callable) {
|
||||||
try {
|
try {
|
||||||
return Result.ok(callable.call());
|
return Result.ok(callable.call());
|
||||||
|
} catch (final Throwable e) {
|
||||||
|
return new Err<>(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a {@code ResultVoid} after calling a {@link VoidCallable}
|
||||||
|
* that produces no output.
|
||||||
|
*
|
||||||
|
* <p>If the {@code callable} completes successfully then a
|
||||||
|
* {@link SuccessVoid} will be returned. if the {@code callable} throws an
|
||||||
|
* exception, then a {@link ErrVoid} containing the exception will be
|
||||||
|
* returned.</p>
|
||||||
|
*
|
||||||
|
* <pre><code>
|
||||||
|
* ResultVoid okay = Result.ofVoid(() -> System.out.println("Hello, World!"));
|
||||||
|
* ResultVoid error = Result.ofVoid(() -> {
|
||||||
|
* throw new Exception();
|
||||||
|
* });
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* @param callable the callable to call
|
||||||
|
* @return a Result with no value
|
||||||
|
*/
|
||||||
|
@API(status = STABLE)
|
||||||
|
static ResultVoid ofVoid(final VoidCallable callable) {
|
||||||
|
try {
|
||||||
|
callable.call();
|
||||||
|
return Result.ok();
|
||||||
} catch (final Throwable e) {
|
} catch (final Throwable e) {
|
||||||
return Result.error(e);
|
return Result.error(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a Result for a success.
|
* Create a Result for an error.
|
||||||
*
|
*
|
||||||
* @param value the value
|
* <pre><code>
|
||||||
* @param <T> the type of the value
|
* ResultVoid error = Result.error(new RuntimeException());
|
||||||
* @return a successful Result
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* @param error the error (Throwable)
|
||||||
|
* @return an error Result
|
||||||
*/
|
*/
|
||||||
default <T> Result<T> success(final T value) {
|
@API(status = STABLE)
|
||||||
return new Success<>(value);
|
static ResultVoid error(final Throwable error) {
|
||||||
|
return new ErrVoid(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a Result for a success.
|
* Create a Result for an error.
|
||||||
*
|
*
|
||||||
* @param value the value
|
* <pre><code>
|
||||||
* @param <T> the type of the value
|
* Result<Integer> error = Result.error(TypeReference.create(), new RuntimeException());
|
||||||
* @return a successful Result
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* @param type the type of the missing) value
|
||||||
|
* @param error the error (Throwable)
|
||||||
|
* @param <R> The type of the missing value
|
||||||
|
* @return an error Result
|
||||||
*/
|
*/
|
||||||
static <T> Result<T> ok(final T value) {
|
@API(status = STABLE)
|
||||||
return new Success<>(value);
|
@SuppressFBWarnings(value = "UP_UNUSED_PARAMETER",
|
||||||
|
justification = "Use the type parameter to fingerprint the return type")
|
||||||
|
static <R> Result<R> error(final TypeReference<R> type, final Throwable error) {
|
||||||
|
return new Err<>(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@link Maybe} from the Result, where the Result is a success, then the Maybe will contain the value.
|
* Creates a Result from the Either, where the Result will be an error if
|
||||||
|
* the Either is a Left, and a success if it is a Right.
|
||||||
*
|
*
|
||||||
* <p>However, if the Result is an error then the Maybe will be nothing.</p>
|
* <pre><code>
|
||||||
|
* import net.kemitix.mon.experimental.either.Either;
|
||||||
*
|
*
|
||||||
* @param result the Result the might contain the value of the Result
|
* Either<Throwable, String> eitherRight = Either.right("Hello, World!");
|
||||||
* @param <T> the type of the Maybe and the Result
|
* Either<Throwable, String> eitherLeft = Either.left(new RuntimeException());
|
||||||
|
*
|
||||||
|
* Result<String> success = Result.from(eitherRight);
|
||||||
|
* Result<String> error = Result.from(eitherLeft);
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* @param either the either that could contain an error in left or a value in right
|
||||||
|
* @param <T> the type of the right value
|
||||||
|
* @return a Result containing the right value of the Either when it is a
|
||||||
|
* Right, or the left error when it is a Left.
|
||||||
|
*/
|
||||||
|
@API(status = EXPERIMENTAL)
|
||||||
|
static <T> Result<T> from(Either<Throwable, T> either) {
|
||||||
|
return Result.from(
|
||||||
|
Maybe.fromOptional(either.getRight()),
|
||||||
|
() -> either.getLeft().get());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a Result from the Maybe, where the Result will be an error if the Maybe is Nothing.
|
||||||
|
*
|
||||||
|
* <p>Where the {@code Maybe} is nothing, then the Supplier will provide the error for the Result.</p>
|
||||||
|
*
|
||||||
|
* <pre><code>
|
||||||
|
* Maybe<Integer> maybe = Maybe.maybe(1);
|
||||||
|
* Result<Integer> result = Result.from(maybe, () -> new RuntimeException());</p>
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* @param maybe the Maybe the might contain the value of the Result
|
||||||
|
* @param error the error that will be the Result if maybe is Nothing
|
||||||
|
* @param <T> the type of the value in the Maybe and the Result
|
||||||
* @return a Result containing the value of the Maybe when it is a Just, or the error when it is Nothing
|
* @return a Result containing the value of the Maybe when it is a Just, or the error when it is Nothing
|
||||||
*/
|
*/
|
||||||
static <T> Maybe<T> toMaybe(final Result<T> result) {
|
@API(status = EXPERIMENTAL)
|
||||||
try {
|
static <T> Result<T> from(final Maybe<T> maybe, final Supplier<Throwable> error) {
|
||||||
return Maybe.just(result.orElseThrow());
|
return maybe.map(Result::ok)
|
||||||
} catch (final CheckedErrorResultException throwable) {
|
.orElseGet(() -> new Err<>(error.get()));
|
||||||
return Maybe.nothing();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// END Static Constructors
|
||||||
|
// BEGIN Static methods
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies a function to a stream of values, folding the results using the
|
||||||
|
* zero value and accumulator function.
|
||||||
|
*
|
||||||
|
* <p>Returns a success {@code Result} of the accumulated outputs if all
|
||||||
|
* values were transformed successfully by the function, or an error
|
||||||
|
* {@code Result} for the first error. If any value results in an error when applying the function, then
|
||||||
|
* processing stops and a Result containing that error is returned,</p>
|
||||||
|
*
|
||||||
|
* <pre><code>
|
||||||
|
* Function<String, Integer> f = s -> {
|
||||||
|
* if ("dd".equals(s)) {
|
||||||
|
* throw new RuntimeException("Invalid input: " + s);
|
||||||
|
* }
|
||||||
|
* return s.length();
|
||||||
|
* };
|
||||||
|
*
|
||||||
|
* Stream<String> okayStream = Stream.of("aa", "bb");
|
||||||
|
* Result<Integer> resultOkay = Result.applyOver(okayStream, f, 0, Integer::sum);
|
||||||
|
* resultOkay.match(
|
||||||
|
* success -> System.out.println("Total length: " + success),
|
||||||
|
* error -> System.out.println("Error: " + error.getMessage())
|
||||||
|
* );
|
||||||
|
* // Total length: 4
|
||||||
|
*
|
||||||
|
* Stream<String> errorStream = Stream.of("cc", "dd");
|
||||||
|
* Result<Integer> resultError = Result.applyOver(errorStream, f, 0, Integer::sum);
|
||||||
|
* resultError.match(
|
||||||
|
* success -> System.out.println("Total length: " + success), // will not match
|
||||||
|
* error -> System.out.println("Error: " + error.getMessage())
|
||||||
|
* );
|
||||||
|
* // Error: Invalid input: dd
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* @param stream the values to apply the function to
|
||||||
|
* @param f the function to apply to the values
|
||||||
|
* @param zero the initial value to use with the accumulator
|
||||||
|
* @param accumulator the function to combine function outputs together
|
||||||
|
* @param <N> the type of the stream values
|
||||||
|
* @param <R> the type of the output value
|
||||||
|
* @return a Success Result of the accumulated function outputs if all
|
||||||
|
* values were transformed successfully by the function, or an Err Result
|
||||||
|
* for the first value that failed.
|
||||||
|
*/
|
||||||
|
@API(status = STABLE)
|
||||||
|
static <N, R> Result<R> applyOver(
|
||||||
|
Stream<N> stream,
|
||||||
|
Function<N, R> f,
|
||||||
|
R zero,
|
||||||
|
BiFunction<R, R, R> accumulator
|
||||||
|
) {
|
||||||
|
var acc = new AtomicReference<>(Result.ok(zero));
|
||||||
|
stream.map(t -> Result.of(() -> f.apply(t)))
|
||||||
|
.peek(r ->
|
||||||
|
r.onSuccess(vNew ->
|
||||||
|
acc.getAndUpdate(rResult ->
|
||||||
|
rResult.map(vOld ->
|
||||||
|
accumulator.apply(vNew, vOld)))))
|
||||||
|
.dropWhile(Result::isOkay)
|
||||||
|
.limit(1)
|
||||||
|
.forEach(acc::set);
|
||||||
|
return acc.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts the successful value from the result, or throws the error within a {@link CheckedErrorResultException}.
|
* Applies a consumer to a stream of values.
|
||||||
*
|
*
|
||||||
* @return the value if a success
|
* <p>If any value results in an error when accepted by the consumer, then
|
||||||
* @throws CheckedErrorResultException if the result is an error
|
* processing stops and a Result containing that error is returned,</p>
|
||||||
|
*
|
||||||
|
* <p>Returns a success Result (with no value) if all values were consumed
|
||||||
|
* successfully by the function, or an error Result for the first value that
|
||||||
|
* failed.</p>
|
||||||
|
*
|
||||||
|
* <pre><code>
|
||||||
|
* List<String> processed = new ArrayList<>();
|
||||||
|
* Consumer<String> consumer = s -> {
|
||||||
|
* if ("dd".equals(s)) {
|
||||||
|
* throw new RuntimeException("Invalid input: " + s);
|
||||||
|
* }
|
||||||
|
* processed.add(s);
|
||||||
|
* };
|
||||||
|
*
|
||||||
|
* Stream<String> okayStream = Stream.of("aa", "bb");
|
||||||
|
* ResultVoid resultOkay = Result.applyOver(okayStream, consumer);
|
||||||
|
* resultOkay.match(
|
||||||
|
* () -> System.out.println("All processed okay."),
|
||||||
|
* error -> System.out.println("Error: " + error.getMessage())
|
||||||
|
* );
|
||||||
|
* System.out.println("Processed: " + processed);
|
||||||
|
* // All processed okay.
|
||||||
|
* // Processed: [aa, bb]
|
||||||
|
*
|
||||||
|
* processed.add("--");
|
||||||
|
* Stream<String> errorStream = Stream.of("cc", "dd", "ee");// fails at 'dd'
|
||||||
|
* ResultVoid resultError = Result.applyOver(errorStream, consumer);
|
||||||
|
* resultError.match(
|
||||||
|
* () -> System.out.println("All processed okay."),
|
||||||
|
* error -> System.out.println("Error: " + error.getMessage())
|
||||||
|
* );
|
||||||
|
* System.out.println("Processed: " + processed);
|
||||||
|
* // Error: Invalid input: dd
|
||||||
|
* // Processed: [aa, bb, --, cc]
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* @param stream the value to supply to the consumer
|
||||||
|
* @param consumer the consumer to receive the values
|
||||||
|
* @param <N> the type of the stream values
|
||||||
|
* @return a Success Result (with no value) if all values were transformed
|
||||||
|
* successfully by the function, or an Err Result for the first value that
|
||||||
|
* failed.
|
||||||
*/
|
*/
|
||||||
T orElseThrow() throws CheckedErrorResultException;
|
@API(status = STABLE)
|
||||||
|
static <N> ResultVoid applyOver(
|
||||||
/**
|
Stream<N> stream,
|
||||||
* Extracts the successful value from the result, or throws the error Throwable.
|
Consumer<N> consumer
|
||||||
*
|
) {
|
||||||
* @param type the type of checked exception that may be thrown
|
return applyOver(stream, n -> {
|
||||||
* @param <E> the type of the checked exception to throw
|
consumer.accept(n);
|
||||||
*
|
return null;
|
||||||
* @return the value if a success
|
}, null, (unused1, unused2) -> null)
|
||||||
* @throws E if the result is an error
|
.toVoid();
|
||||||
*/
|
|
||||||
<E extends Exception> T orElseThrow(Class<E> type) throws E;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts the successful value from the result, or throws the error in a {@link UnexpectedErrorResultException}.
|
|
||||||
*
|
|
||||||
* @return the value if a success
|
|
||||||
*/
|
|
||||||
T orElseThrowUnchecked();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Swaps the inner Result of a Maybe, so that a Result is on the outside.
|
|
||||||
*
|
|
||||||
* @param maybeResult the Maybe the contains a Result
|
|
||||||
* @param <T> the type of the value that may be in the Result
|
|
||||||
* @return a Result containing a Maybe, the value in the Maybe was the value in a successful Result within the
|
|
||||||
* original Maybe. If the original Maybe is Nothing, the Result will contain Nothing. If the original Result was an
|
|
||||||
* error, then the Result will also be an error.
|
|
||||||
*/
|
|
||||||
static <T> Result<Maybe<T>> swap(final Maybe<Result<T>> maybeResult) {
|
|
||||||
return maybeResult.orElseGet(() -> Result.ok(null))
|
|
||||||
.flatMap(value -> Result.ok(Maybe.maybe(value)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a new Result consisting of the result of applying the function to the contents of the Result.
|
* Applies a function to a stream of values, folding the results using the
|
||||||
|
* zero value and accumulator function.
|
||||||
*
|
*
|
||||||
* @param f the mapping function the produces a Result
|
* <p>If any value results in an error when applying the function, then
|
||||||
* @param <R> the type of the value withing the Result of the mapping function
|
* processing stops and a {@code Result} containing that error is returned.</p>
|
||||||
* @return a Result
|
*
|
||||||
|
* <p>Returns a success {@code Result} of the accumulated function outputs
|
||||||
|
* if all values were transformed successfully, or an error {@code Result}
|
||||||
|
* for the first value that failed.</p>
|
||||||
|
*
|
||||||
|
* <p>Similar to {@link #applyOver(Stream, Function, Object, BiFunction)},
|
||||||
|
* except that the result of the {@code f} function is a {@code Result}; and
|
||||||
|
* to a {@code flatMap} method in that the {@code Result} is not nested with
|
||||||
|
* in another {@code Result}.</p>
|
||||||
|
*
|
||||||
|
* <pre><code>
|
||||||
|
* Function<String, Integer> f = s -> {
|
||||||
|
* if ("dd".equals(s)) {
|
||||||
|
* throw new RuntimeException("Invalid input: " + s);
|
||||||
|
* }
|
||||||
|
* return s.length();
|
||||||
|
* };
|
||||||
|
*
|
||||||
|
* Stream<String> okayStream = Stream.of("aa", "bb");
|
||||||
|
* Result<Integer> resultOkay = Result.applyOver(okayStream, f, 0, Integer::sum);
|
||||||
|
* resultOkay.match(
|
||||||
|
* success -> assertThat(success).isEqualTo(4),
|
||||||
|
* error -> fail("not an err")
|
||||||
|
* );
|
||||||
|
* // Total length: 4
|
||||||
|
*
|
||||||
|
* Stream<String> errorStream = Stream.of("cc", "dd");
|
||||||
|
* Result<Integer> resultError = Result.applyOver(errorStream, f, 0, Integer::sum);
|
||||||
|
* resultError.match(
|
||||||
|
* success -> fail("not a success"), // will not match
|
||||||
|
* error -> assertThat(error.getMessage()).isEqualTo("Invalid input: dd")
|
||||||
|
* );
|
||||||
|
* // Error: Invalid input: dd
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* @param stream the values to apply the function to
|
||||||
|
* @param f the function to apply to the values
|
||||||
|
* @param zero the initial value to use with the accumulator
|
||||||
|
* @param accumulator the function to combine function outputs together
|
||||||
|
* @param <T> the type of the stream values
|
||||||
|
* @param <R> the type of the output value
|
||||||
|
* @return a Success Result of the accumulated function outputs if all
|
||||||
|
* values were transformed successfully by the function, or an Err Result
|
||||||
|
* for the first value that failed.
|
||||||
*/
|
*/
|
||||||
<R> Result<R> flatMap(Function<T, Result<R>> f);
|
@API(status = STABLE)
|
||||||
|
static <T, R> Result<R> flatApplyOver(
|
||||||
|
Stream<T> stream,
|
||||||
|
Function<T, Result<R>> f,
|
||||||
|
R zero,
|
||||||
|
BiFunction<R, R, R> accumulator
|
||||||
|
) {
|
||||||
|
var acc = new AtomicReference<>(Result.ok(zero));
|
||||||
|
stream.map(f)
|
||||||
|
.peek(r -> r.onSuccess(vNew ->
|
||||||
|
acc.getAndUpdate(rResult ->
|
||||||
|
rResult.map(vOld ->
|
||||||
|
accumulator.apply(vNew, vOld)))))
|
||||||
|
.dropWhile(Result::isOkay)
|
||||||
|
.limit(1)
|
||||||
|
.forEach(acc::set);
|
||||||
|
return acc.get();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies the function to the contents of a Maybe within the Result.
|
* Applies the function to the contents of a {@link Maybe} within the {@code Result}.
|
||||||
|
*
|
||||||
|
* <pre><code>
|
||||||
|
* Result<Maybe<Integer>> result = Result.of(() -> Maybe.maybe(getValue()));
|
||||||
|
* Result<Maybe<Integer>> maybeResult = Result.flatMapMaybe(result,
|
||||||
|
* maybe -> Result.of(() -> maybe.map(v -> v * 2)));
|
||||||
|
* </code></pre>
|
||||||
*
|
*
|
||||||
* @param maybeResult the Result that may contain a value
|
* @param maybeResult the Result that may contain a value
|
||||||
* @param f the function to apply to the value
|
* @param f the function to apply to the value
|
||||||
|
@ -200,6 +438,7 @@ public interface Result<T> extends Functor<T, Result<?>> {
|
||||||
* @param <R> the type of the updated Result
|
* @param <R> the type of the updated Result
|
||||||
* @return a new Maybe within a Result
|
* @return a new Maybe within a Result
|
||||||
*/
|
*/
|
||||||
|
@API(status = EXPERIMENTAL)
|
||||||
static <T, R> Result<Maybe<R>> flatMapMaybe(
|
static <T, R> Result<Maybe<R>> flatMapMaybe(
|
||||||
final Result<Maybe<T>> maybeResult,
|
final Result<Maybe<T>> maybeResult,
|
||||||
final Function<Maybe<T>, Result<Maybe<R>>> f
|
final Function<Maybe<T>, Result<Maybe<R>>> f
|
||||||
|
@ -208,63 +447,296 @@ public interface Result<T> extends Functor<T, Result<?>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the Result is an error.
|
* Swaps the inner {@code Result} of a {@link Maybe}, so that a {@code Result} contains a {@code Maybe}.
|
||||||
*
|
*
|
||||||
* @return true if the Result is an error.
|
* @param maybeResult the Maybe the contains a Result
|
||||||
|
* @param <T> the type of the value that may be in the Result
|
||||||
|
* @return a Result containing a Maybe, the value in the Maybe was the value in a successful Result within the
|
||||||
|
* original Maybe. If the original Maybe is Nothing, the Result will contain Nothing. If the original Result was an
|
||||||
|
* error, then the Result will also be an error.
|
||||||
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
boolean isError();
|
@API(status = DEPRECATED)
|
||||||
|
@Deprecated
|
||||||
|
static <T> Result<Maybe<T>> swap(final Maybe<Result<T>> maybeResult) {
|
||||||
|
return maybeResult.orElseGet(() -> Result.ok(null))
|
||||||
|
.flatMap(value -> Result.ok(Maybe.maybe(value)));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the Result is a success.
|
* Creates a {@link Maybe} from the {@code Result}.
|
||||||
*
|
*
|
||||||
* @return true if the Result is a success.
|
* <p>Where the {@code Result} is a {@link Success}, the {@code Maybe} will be a {@code Just} contain the value of
|
||||||
|
* the {@code Result}.</p>
|
||||||
|
*
|
||||||
|
* <p>However, if the {@code Result} is an {@link Err}, then the {@code Maybe} will be {@code Nothing}.</p>
|
||||||
|
*
|
||||||
|
* <pre><code>
|
||||||
|
* Result<Integer> result = Result.of(() -> getValue());
|
||||||
|
* Maybe<Integer> maybe = Result.toMaybe(result);
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* @param result the Result the might contain the value of the Result
|
||||||
|
* @param <T> the type of the Maybe and the Result
|
||||||
|
* @return a Result containing the value of the Maybe when it is a Just, or the error when it is Nothing
|
||||||
*/
|
*/
|
||||||
boolean isOkay();
|
@API(status = EXPERIMENTAL)
|
||||||
|
static <T> Maybe<T> toMaybe(final Result<T> result) {
|
||||||
|
try {
|
||||||
|
return Maybe.just(result.orElseThrow());
|
||||||
|
} catch (final CheckedErrorResultException throwable) {
|
||||||
|
return Maybe.nothing();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// END Static methods
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a {@link Result} for the output of the {@link Callable}.
|
||||||
|
*
|
||||||
|
* <p>If the {@code Callable} succeeds then the {@code Result} will be a
|
||||||
|
* {@link Success} and will contain the value.
|
||||||
|
* If it throws an {@code Exception}, then the {@code Result} will be an
|
||||||
|
* {@link Err} and will contain that exception.</p>
|
||||||
|
*
|
||||||
|
* <pre><code>
|
||||||
|
* Result<Integer> start = Result.ok(1);
|
||||||
|
* Result<Integer> okay = start.result(() -> 1);
|
||||||
|
* Result<Integer> error = start.result(() -> {
|
||||||
|
* throw new RuntimeException();
|
||||||
|
* });
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* @param callable the callable to produce the result
|
||||||
|
* @param <R> the type of the value
|
||||||
|
* @return a Result
|
||||||
|
*/
|
||||||
|
@API(status = EXPERIMENTAL)
|
||||||
|
default <R> Result<R> result(final Callable<R> callable) {
|
||||||
|
return Result.of(callable);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the {@code Result} into an {@link Either}.
|
||||||
|
*
|
||||||
|
* <pre><code>
|
||||||
|
* Result<String> success = Result.ok("success");
|
||||||
|
* RuntimeException exception = new RuntimeException();
|
||||||
|
* Result<String> error = Result.error(String.class, exception);
|
||||||
|
*
|
||||||
|
* Either<Throwable, String> eitherRight = success.toEither();
|
||||||
|
* Either<Throwable, String> eitherLeft = error.toEither();
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* @return A {@code Right} for a success or a {@code Left} for an error.
|
||||||
|
*/
|
||||||
|
@API(status = EXPERIMENTAL)
|
||||||
|
default Either<Throwable, T> toEither() {
|
||||||
|
var either = new AtomicReference<Either<Throwable, T>>();
|
||||||
|
match(
|
||||||
|
success -> either.set(Either.right(success)),
|
||||||
|
error -> either.set(Either.left(error))
|
||||||
|
);
|
||||||
|
return either.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts the successful value from the result, or throws a {@link CheckedErrorResultException} with the error
|
||||||
|
* as the cause.
|
||||||
|
*
|
||||||
|
* <pre><code>
|
||||||
|
* Integer result = Result.of(() -> getValue())
|
||||||
|
* .orElseThrow();
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* @return the value if a success
|
||||||
|
* @throws CheckedErrorResultException if the result is an error
|
||||||
|
*/
|
||||||
|
@API(status = STABLE)
|
||||||
|
T orElseThrow() throws CheckedErrorResultException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the successful value from the result, or throws the error if it is an instance of the type specified,
|
||||||
|
* otherwise it will throw an {@link UnexpectedErrorResultException} with the error as the cause.
|
||||||
|
*
|
||||||
|
* <pre><code>
|
||||||
|
* Integer result = Result.of(() -> getValue())
|
||||||
|
* .orElseThrow(IOException.class);
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* @param type the type of checked exception that may be thrown
|
||||||
|
* @param <E> the type of the checked exception to throw
|
||||||
|
* @return the value if a success
|
||||||
|
* @throws E if the result is an error
|
||||||
|
*/
|
||||||
|
@API(status = STABLE)
|
||||||
|
<E extends Exception> T orElseThrow(Class<E> type) throws E;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the successful value from the result, or throws an {@link ErrorResultException}, an unchecked exception,
|
||||||
|
* with the error as the cause.
|
||||||
|
*
|
||||||
|
* <pre><code>
|
||||||
|
* Integer result = Result.of(() -> getValue())
|
||||||
|
* .orElseThrowUnchecked();
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* @return the value if a success
|
||||||
|
*/
|
||||||
|
@API(status = STABLE)
|
||||||
|
T orElseThrowUnchecked();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies the function to the value within the {@code Result} and returns the result if this is a success,
|
||||||
|
* otherwise returns a new {@code Result} with the existing error.
|
||||||
|
*
|
||||||
|
* <pre><code>
|
||||||
|
* Result<String> result = Result.of(() -> getValue())
|
||||||
|
* .flatMap(v -> Result.of(() -> String.valueOf(v)));
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* @param f the mapping function the produces a Result
|
||||||
|
* @param <R> the type of the value withing the Result of the mapping function
|
||||||
|
* @return a Result
|
||||||
|
*/
|
||||||
|
@API(status = STABLE)
|
||||||
|
<R> Result<R> flatMap(Function<T, Result<R>> f);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies the function to the value within the {@code Result} and returns the void result if this is a success,
|
||||||
|
* otherwise returns a new {@code Result} with the existing error.
|
||||||
|
*
|
||||||
|
* <pre><code>
|
||||||
|
* ResultVoid result = Result.of(() -> getValue())
|
||||||
|
* .flatMapV(v -> Result.ok());
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* @param f the mapping function the produces a ResultVoid
|
||||||
|
* @return a ResultVoid
|
||||||
|
*/
|
||||||
|
@API(status = STABLE)
|
||||||
|
ResultVoid flatMapV(Function<T, ResultVoid> f);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies the function to the value within the {@code Result}, returning
|
||||||
|
* the result within another {@code Result}.
|
||||||
|
*
|
||||||
|
* <p>If the initial {@code Result} is a success, then apply the function to
|
||||||
|
* the value within the {@code Result}, returning the result within another
|
||||||
|
* {@code Result}. If the initial {@code Result} is an error, then return
|
||||||
|
* another error without invoking the supplied function.</p>
|
||||||
|
*
|
||||||
|
* <p>If the supplied function throws an exception, then an error
|
||||||
|
* {@code Result} will be returned containing that exception.</p>
|
||||||
|
*
|
||||||
|
* <pre><code>
|
||||||
|
* Result<String> result = Result.of(() -> getValue())
|
||||||
|
* .map(v -> String.valueOf(v));
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* @param f the function to apply
|
||||||
|
* @param <R> the type of the value returned by the function to be applied
|
||||||
|
* @return A {@code Result} containing either the original error, the
|
||||||
|
* function output, or any exception thrown by the supplied function.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
<R> Result<R> map(Function<T, R> f);
|
@API(status = STABLE)
|
||||||
|
<R> Result<R> map(ThrowableFunction<T, R, ?> f);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Matches the Result, either success or error, and supplies the appropriate Consumer with the value or error.
|
* Matches the Result, either success or error, and supplies the appropriate
|
||||||
|
* Consumer with the value or error.
|
||||||
|
*
|
||||||
|
* <pre><code>
|
||||||
|
* Result.of(()-> getValue())
|
||||||
|
* .match(
|
||||||
|
* success -> doSomething(success),
|
||||||
|
* error -> handleError(error)
|
||||||
|
* );
|
||||||
|
* </code></pre>
|
||||||
*
|
*
|
||||||
* @param onSuccess the Consumer to pass the value of a successful Result to
|
* @param onSuccess the Consumer to pass the value of a successful Result to
|
||||||
* @param onError the Consumer to pass the error from an error Result to
|
* @param onError the Consumer to pass the error from an error Result to
|
||||||
*/
|
*/
|
||||||
|
@API(status = STABLE)
|
||||||
void match(Consumer<T> onSuccess, Consumer<Throwable> onError);
|
void match(Consumer<T> onSuccess, Consumer<Throwable> onError);
|
||||||
|
|
||||||
/**
|
|
||||||
* Wraps the value within the Result in a Maybe, either a Just if the predicate is true, or Nothing.
|
|
||||||
*
|
|
||||||
* @param predicate the test to decide
|
|
||||||
* @return a Result containing a Maybe that may or may not contain a value
|
|
||||||
*/
|
|
||||||
Result<Maybe<T>> maybe(Predicate<T> predicate);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provide the value within the Result, if it is a success, to the Consumer, and returns this Result.
|
* Provide the value within the Result, if it is a success, to the Consumer, and returns this Result.
|
||||||
*
|
*
|
||||||
|
* <pre><code>
|
||||||
|
* Result<Integer> result = Result.of(() -> getValue())
|
||||||
|
* .peek(v -> System.out.println(v));
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
* @param consumer the Consumer to the value if a success
|
* @param consumer the Consumer to the value if a success
|
||||||
* @return this Result
|
* @return this Result
|
||||||
*/
|
*/
|
||||||
|
@API(status = STABLE)
|
||||||
Result<T> peek(Consumer<T> consumer);
|
Result<T> peek(Consumer<T> consumer);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provide a way to attempt to recover from an error state.
|
* Attempts to restore an error {@code Result} to a success.
|
||||||
|
*
|
||||||
|
* <p>When the Result is already a success, then the result is returned
|
||||||
|
* unmodified.</p>
|
||||||
|
*
|
||||||
|
* <pre><code>
|
||||||
|
* Result<Integer> result = Result.of(() -> getValue())
|
||||||
|
* .recover(e -> Result.of(() -> getSafeValue(e)));
|
||||||
|
* </code></pre>
|
||||||
*
|
*
|
||||||
* @param f the function to recover from the error
|
* @param f the function to recover from the error
|
||||||
* @return a new Result, either a Success, or if recovery is not possible an other Err.
|
* @return if Result is an error, a new Result, either a Success, or if
|
||||||
|
* recovery is not possible another error. If the Result is already a
|
||||||
|
* success, then this returns itself.
|
||||||
*/
|
*/
|
||||||
|
@API(status = STABLE)
|
||||||
Result<T> recover(Function<Throwable, Result<T>> f);
|
Result<T> recover(Function<Throwable, Result<T>> f);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A handler for error states.
|
* A handler for success states.
|
||||||
*
|
*
|
||||||
* <p>When this is an error then tne Consumer will be supplier with the error. When this is a success, then nothing
|
* <pre><code>
|
||||||
* happens.</p>
|
* void handleSuccess(Integer value) {...}
|
||||||
|
* Result.of(() -> getValue())
|
||||||
|
* .onSuccess(v -> handleSuccess(v));
|
||||||
|
* </code></pre>
|
||||||
*
|
*
|
||||||
* @param errorConsumer the consumer to handle the error
|
* <p>When this is a success then tne Consumer will be supplied with the
|
||||||
|
* success value. When this is an error, then nothing happens.</p>
|
||||||
|
*
|
||||||
|
* @param successConsumer the consumer to handle the success
|
||||||
*/
|
*/
|
||||||
void onError(Consumer<Throwable> errorConsumer);
|
@API(status = STABLE)
|
||||||
|
void onSuccess(Consumer<T> successConsumer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A handler for error state, when the error matches the errorClass.
|
||||||
|
*
|
||||||
|
* <p>If the `Result` is an error and that error is an instance of the
|
||||||
|
* errorClass, then supply the error to the `Consumer`. Does nothing if the
|
||||||
|
* error is not an instance of the errorClass, or is a success.</p>
|
||||||
|
*
|
||||||
|
* <p>Similar to the catch block in a try-catch.</p>
|
||||||
|
*
|
||||||
|
* <pre><code>
|
||||||
|
* void handleError(UnsupportedOperationException e) {...}
|
||||||
|
* Result.of(() -> getValue())
|
||||||
|
* .onError(UnsupportedOperationException.class,
|
||||||
|
* e -> handleError(e))
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* @param errorClass the class of Throwable to match
|
||||||
|
* @param consumer the consumer to call if it matches
|
||||||
|
* @param <E> the Type of the Throwable to match
|
||||||
|
* @return the original unmodified Result
|
||||||
|
*/
|
||||||
|
@API(status = STABLE)
|
||||||
|
<E extends Throwable> Result<T> onError(
|
||||||
|
Class<E> errorClass,
|
||||||
|
Consumer<E> consumer
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maps a Success Result to another Result using a Callable that is able to throw a checked exception.
|
* Maps a Success Result to another Result using a Callable that is able to throw a checked exception.
|
||||||
|
@ -283,9 +755,38 @@ public interface Result<T> extends Functor<T, Result<?>> {
|
||||||
* @param f the function to map the Success value into the Callable
|
* @param f the function to map the Success value into the Callable
|
||||||
* @param <R> the type of the final Result
|
* @param <R> the type of the final Result
|
||||||
* @return a new Result
|
* @return a new Result
|
||||||
|
* @deprecated Use {@link #map(ThrowableFunction)}
|
||||||
*/
|
*/
|
||||||
|
@API(status = DEPRECATED)
|
||||||
|
@Deprecated
|
||||||
<R> Result<R> andThen(Function<T, Callable<R>> f);
|
<R> Result<R> andThen(Function<T, Callable<R>> f);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform the continuation with the value within the success {@code Result}
|
||||||
|
* and return itself.
|
||||||
|
*
|
||||||
|
* <p>Where the {@code Result} is a success, then if an exception is thrown
|
||||||
|
* by the continuation the {@code Result} returned will be a new error
|
||||||
|
* {@code Result} containing that exception, otherwise the original
|
||||||
|
* {@code Result}will be returned.</p>
|
||||||
|
|
||||||
|
* <p>Where the {@code Result} is an error, then the {@code Result} is
|
||||||
|
* returned immediately and the continuation is ignored.</p>
|
||||||
|
*
|
||||||
|
* <pre><code>
|
||||||
|
* Integer doSomething() {...}
|
||||||
|
* void doSomethingElse(final Integer value) {...}
|
||||||
|
* Result<Integer> r = Result.of(() -> doSomething())
|
||||||
|
* .thenWith(value -> () -> doSomethingElse(value));
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* @param f the function to map the Success value into the result
|
||||||
|
* continuation
|
||||||
|
* @return the Result or a new error Result
|
||||||
|
*/
|
||||||
|
@API(status = STABLE)
|
||||||
|
Result<T> thenWith(Function<T, WithResultContinuation<T>> f);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Perform the continuation with the current Result value then return the current Result, assuming there was no
|
* Perform the continuation with the current Result value then return the current Result, assuming there was no
|
||||||
* error in the continuation.
|
* error in the continuation.
|
||||||
|
@ -304,7 +805,8 @@ public interface Result<T> extends Functor<T, Result<?>> {
|
||||||
* @param f the function to map the Success value into the result continuation
|
* @param f the function to map the Success value into the result continuation
|
||||||
* @return the Result or a new error Result
|
* @return the Result or a new error Result
|
||||||
*/
|
*/
|
||||||
Result<T> thenWith(Function<T, WithResultContinuation<T>> f);
|
@API(status = STABLE)
|
||||||
|
ResultVoid thenWithV(Function<T, WithResultContinuation<T>> f);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reduce two Results of the same type into one using the reducing function provided.
|
* Reduce two Results of the same type into one using the reducing function provided.
|
||||||
|
@ -312,9 +814,24 @@ public interface Result<T> extends Functor<T, Result<?>> {
|
||||||
* <p>If either Result is an error, then the reduce will return the error. If both are errors, then the error of
|
* <p>If either Result is an error, then the reduce will return the error. If both are errors, then the error of
|
||||||
* {@code this} Result will be returned.</p>
|
* {@code this} Result will be returned.</p>
|
||||||
*
|
*
|
||||||
* @param identify the identify Result
|
* @param identify the identity Result
|
||||||
* @param operator the function to combine the values the Results
|
* @param operator the function to combine the values the Results
|
||||||
* @return a Result containing the combination of the two Results
|
* @return a Result containing the combination of the two Results
|
||||||
*/
|
*/
|
||||||
|
@API(status = EXPERIMENTAL)
|
||||||
Result<T> reduce(Result<T> identify, BinaryOperator<T> operator);
|
Result<T> reduce(Result<T> identify, BinaryOperator<T> operator);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Discard any success value while retaining any error.
|
||||||
|
*
|
||||||
|
* <pre><code>
|
||||||
|
* ResultVoid result = Result.of(() -> getResultValue())
|
||||||
|
* .toVoid();
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* @return A {@code SuccessVoid} for a {@code Success} or a {@code ErrVoid} for an {@code Err}.
|
||||||
|
*/
|
||||||
|
@API(status = STABLE)
|
||||||
|
ResultVoid toVoid();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
134
src/main/java/net/kemitix/mon/result/ResultVoid.java
Normal file
134
src/main/java/net/kemitix/mon/result/ResultVoid.java
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
package net.kemitix.mon.result;
|
||||||
|
|
||||||
|
import org.apiguardian.api.API;
|
||||||
|
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import static org.apiguardian.api.API.Status.STABLE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A @{link Result} with no value.
|
||||||
|
*/
|
||||||
|
public interface ResultVoid extends BaseResult {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Matches the Result, either success or error, and supplies the appropriate
|
||||||
|
* Consumer with the value or error.
|
||||||
|
*
|
||||||
|
* <pre><code>
|
||||||
|
* Result.ok()
|
||||||
|
* .match(
|
||||||
|
* () -> doSomething(),
|
||||||
|
* error -> handleError(error)
|
||||||
|
* );
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* @param onSuccess the Consumer to pass the value of a successful Result to
|
||||||
|
* @param onError the Consumer to pass the error from an error Result to
|
||||||
|
*/
|
||||||
|
@API(status = STABLE)
|
||||||
|
void match(Runnable onSuccess, Consumer<Throwable> onError);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to restore an error {@code ResultVoid} to a success.
|
||||||
|
*
|
||||||
|
* <p>When the Result is already a success, then the result is returned
|
||||||
|
* unmodified.</p>
|
||||||
|
*
|
||||||
|
* <pre><code>
|
||||||
|
* void doSomethingRisky(String s) throws Exception {...}
|
||||||
|
* ResultVoid result = Result.ofVoid(() -> doSomethingRisky("first"))
|
||||||
|
* .recover(e -> Result.ofVoid(() -> doSomethingRisky("second")));
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* @param f the function to recover from the error
|
||||||
|
* @return if Result is an error, a new Result, either a Success, or if
|
||||||
|
* recovery is not possible another error. If the Result is already a
|
||||||
|
* success, then this returns itself.
|
||||||
|
*/
|
||||||
|
@API(status = STABLE)
|
||||||
|
ResultVoid recover(Function<Throwable, ResultVoid> f);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A handler for success states.
|
||||||
|
*
|
||||||
|
* <pre><code>
|
||||||
|
* void doSomethingRisky() throws Exception {...}
|
||||||
|
* void handleSuccess() {...}
|
||||||
|
* Result.ofVoid(() -> doSomethingRisky()) // ResultVoid
|
||||||
|
* .onSuccess(() -> handleSuccess());
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* <p>When this is a success then tne Consumer will be supplied with the
|
||||||
|
* success value. When this is an error, then nothing happens.</p>
|
||||||
|
*
|
||||||
|
* @param runnable the call if the Result is a success
|
||||||
|
*/
|
||||||
|
@API(status = STABLE)
|
||||||
|
void onSuccess(Runnable runnable);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A handler for error state, when the error matches the errorClass.
|
||||||
|
*
|
||||||
|
* <p>If the `Result` is an error and that error is an instance of the
|
||||||
|
* errorClass, then supply the error to the `Consumer`. Does nothing if the
|
||||||
|
* error is not an instance of the errorClass, or is a success.</p>
|
||||||
|
*
|
||||||
|
* <p>Similar to the catch block in a try-catch.</p>
|
||||||
|
*
|
||||||
|
* <pre><code>
|
||||||
|
* void handleError(UnsupportedOperationException e) {...}
|
||||||
|
* Result.of(() -> getValue())
|
||||||
|
* .onError(UnsupportedOperationException.class,
|
||||||
|
* e -> handleError(e))
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* @param errorClass the class of Throwable to match
|
||||||
|
* @param consumer the consumer to call if it matches
|
||||||
|
* @param <E> the Type of the Throwable to match
|
||||||
|
* @return the original unmodified Result
|
||||||
|
*/
|
||||||
|
@API(status = STABLE)
|
||||||
|
<E extends Throwable> ResultVoid onError(
|
||||||
|
Class<E> errorClass,
|
||||||
|
Consumer<E> consumer
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the callable if the {@code Result} is a success, ignore it if is an error.
|
||||||
|
*
|
||||||
|
* <pre><code>
|
||||||
|
* Result.ofVoid(() -> doSomethingRisky())
|
||||||
|
* .andThen(() -> doSomethingRisky("again"));
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* @param f the function to map the Success value into the Callable
|
||||||
|
* @return itself unless the callable fails when it will return a new error Result
|
||||||
|
*/
|
||||||
|
@API(status = STABLE)
|
||||||
|
ResultVoid andThen(VoidCallable f);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replaces the current Result with the result of the callable.
|
||||||
|
*
|
||||||
|
* <p>Discards the success/error state or the current Result.</p>
|
||||||
|
*
|
||||||
|
* <p>If the callable results in a new error, then that error will be in the returned Result.</p>
|
||||||
|
*
|
||||||
|
* <pre><code>
|
||||||
|
* Result<Integer> result = Result.ofVoid(() -> doSomethingRisky())
|
||||||
|
* .inject(() -> 1);
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* @param callable the callable to create the new value
|
||||||
|
* @param <T> the type of the new value
|
||||||
|
* @return a new Result with the result of callable
|
||||||
|
*/
|
||||||
|
default <T> Result<T> inject(Callable<T> callable) {
|
||||||
|
return Result.of(callable);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -22,14 +22,12 @@
|
||||||
package net.kemitix.mon.result;
|
package net.kemitix.mon.result;
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import net.kemitix.mon.maybe.Maybe;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
import java.util.function.BinaryOperator;
|
import java.util.function.BinaryOperator;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Predicate;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Successful Result.
|
* A Successful Result.
|
||||||
|
@ -37,7 +35,8 @@ import java.util.function.Predicate;
|
||||||
* @param <T> the type of the value in the Result
|
* @param <T> the type of the value in the Result
|
||||||
*/
|
*/
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@SuppressWarnings({"methodcount", "PMD.CyclomaticComplexity"})
|
@SuppressWarnings({"methodcount", "PMD.TooManyMethods", "PMD.ExcessivePublicCount",
|
||||||
|
"PMD.CyclomaticComplexity"})
|
||||||
class Success<T> implements Result<T> {
|
class Success<T> implements Result<T> {
|
||||||
|
|
||||||
private final T value;
|
private final T value;
|
||||||
|
@ -54,11 +53,11 @@ class Success<T> implements Result<T> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings({"illegalcatch", "PMD.AvoidCatchingThrowable"})
|
@SuppressWarnings({"illegalcatch", "PMD.AvoidCatchingThrowable"})
|
||||||
public <R> Result<R> map(final Function<T, R> f) {
|
public <R> Result<R> map(final ThrowableFunction<T, R, ?> f) {
|
||||||
try {
|
try {
|
||||||
return success(f.apply(this.value));
|
return new Success<>(f.apply(value));
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
return err(e);
|
return new Err<>(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,14 +66,6 @@ class Success<T> implements Result<T> {
|
||||||
onSuccess.accept(value);
|
onSuccess.accept(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Result<Maybe<T>> maybe(final Predicate<T> predicate) {
|
|
||||||
if (predicate.test(value)) {
|
|
||||||
return success(Maybe.just(value));
|
|
||||||
}
|
|
||||||
return success(Maybe.nothing());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public T orElseThrow() {
|
public T orElseThrow() {
|
||||||
return value;
|
return value;
|
||||||
|
@ -101,11 +92,24 @@ class Success<T> implements Result<T> {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(final Consumer<T> successConsumer) {
|
||||||
|
successConsumer.accept(value);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(final Consumer<Throwable> errorConsumer) {
|
public void onError(final Consumer<Throwable> errorConsumer) {
|
||||||
// do nothing - this is not an error
|
// do nothing - this is not an error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <E extends Throwable> Result<T> onError(
|
||||||
|
final Class<E> errorClass,
|
||||||
|
final Consumer<E> consumer
|
||||||
|
) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <R> Result<R> andThen(final Function<T, Callable<R>> f) {
|
public <R> Result<R> andThen(final Function<T, Callable<R>> f) {
|
||||||
return result(f.apply(value));
|
return result(f.apply(value));
|
||||||
|
@ -116,6 +120,11 @@ class Success<T> implements Result<T> {
|
||||||
return f.apply(value).call(this);
|
return f.apply(value).call(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResultVoid thenWithV(final Function<T, WithResultContinuation<T>> f) {
|
||||||
|
return f.apply(value).call(this).toVoid();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Result<T> reduce(final Result<T> identity, final BinaryOperator<T> operator) {
|
public Result<T> reduce(final Result<T> identity, final BinaryOperator<T> operator) {
|
||||||
return flatMap(a -> identity.flatMap(b -> result(() -> operator.apply(a, b))));
|
return flatMap(a -> identity.flatMap(b -> result(() -> operator.apply(a, b))));
|
||||||
|
@ -126,6 +135,11 @@ class Success<T> implements Result<T> {
|
||||||
return f.apply(value);
|
return f.apply(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResultVoid flatMapV(final Function<T, ResultVoid> f) {
|
||||||
|
return f.apply(value);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(final Object other) {
|
public boolean equals(final Object other) {
|
||||||
return other instanceof Success && Objects.equals(value, ((Success) other).value);
|
return other instanceof Success && Objects.equals(value, ((Success) other).value);
|
||||||
|
@ -140,4 +154,9 @@ class Success<T> implements Result<T> {
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return String.format("Result.Success{value=%s}", value);
|
return String.format("Result.Success{value=%s}", value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResultVoid toVoid() {
|
||||||
|
return SuccessVoid.getInstance();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
91
src/main/java/net/kemitix/mon/result/SuccessVoid.java
Normal file
91
src/main/java/net/kemitix/mon/result/SuccessVoid.java
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
package net.kemitix.mon.result;
|
||||||
|
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
@SuppressWarnings("PMD.AvoidDuplicateLiterals")
|
||||||
|
public class SuccessVoid implements ResultVoid {
|
||||||
|
|
||||||
|
private static final ResultVoid INSTANCE = new SuccessVoid();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the SuccessVoid instance.
|
||||||
|
*
|
||||||
|
* <p>The SuccessVoid, having no value, represents the state of success.</p>
|
||||||
|
*
|
||||||
|
* @return the SuccessVoid
|
||||||
|
*/
|
||||||
|
public static ResultVoid getInstance() {
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isError() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isOkay() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void match(final Runnable onSuccess, final Consumer<Throwable> onError) {
|
||||||
|
onSuccess.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResultVoid recover(final Function<Throwable, ResultVoid> f) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(final Runnable runnable) {
|
||||||
|
runnable.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(final Consumer<Throwable> errorConsumer) {
|
||||||
|
// do nothing - this is not an error
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <E extends Throwable> ResultVoid onError(
|
||||||
|
final Class<E> errorClass,
|
||||||
|
final Consumer<E> consumer
|
||||||
|
) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("PMD.AvoidCatchingGenericException")
|
||||||
|
public ResultVoid andThen(final VoidCallable f) {
|
||||||
|
try {
|
||||||
|
f.call();
|
||||||
|
return this;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return new ErrVoid(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object other) {
|
||||||
|
return other instanceof SuccessVoid;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(SuccessVoid.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Result.SuccessVoid{}";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
23
src/main/java/net/kemitix/mon/result/ThrowableFunction.java
Normal file
23
src/main/java/net/kemitix/mon/result/ThrowableFunction.java
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
package net.kemitix.mon.result;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a function that accepts one argument and produces a result.
|
||||||
|
* This is a functional interface whose functional method is apply(Object).
|
||||||
|
*
|
||||||
|
* @param <T> the type of the input to the function
|
||||||
|
* @param <R> the type of the result of the function
|
||||||
|
* @param <E> the type of the exception that could be thrown
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface ThrowableFunction<T, R, E extends Throwable> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies this function to the given argument.
|
||||||
|
*
|
||||||
|
* @param value the function argument
|
||||||
|
* @return the function result
|
||||||
|
* @throws E if the function fails
|
||||||
|
*/
|
||||||
|
R apply(T value) throws E;
|
||||||
|
|
||||||
|
}
|
19
src/main/java/net/kemitix/mon/result/VoidCallable.java
Normal file
19
src/main/java/net/kemitix/mon/result/VoidCallable.java
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
package net.kemitix.mon.result;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A task that returns void and may throw an exception.
|
||||||
|
*
|
||||||
|
* <p>Implementors define a single method with no arguments called call.
|
||||||
|
* The VoidCallable interface is similar to {@link java.util.concurrent.Callable},
|
||||||
|
* but does not return a value.</p>
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface VoidCallable {
|
||||||
|
/**
|
||||||
|
* Executes and may throw an exception.
|
||||||
|
*
|
||||||
|
* @throws Exception if unable to complete
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("PMD.SignatureDeclareThrowsException")
|
||||||
|
void call() throws Exception;
|
||||||
|
}
|
|
@ -21,6 +21,8 @@
|
||||||
|
|
||||||
package net.kemitix.mon.result;
|
package net.kemitix.mon.result;
|
||||||
|
|
||||||
|
import net.kemitix.mon.TypeReference;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Callable-like interface for performing an action with a Result that, if there are no errors is returned as-is, but
|
* A Callable-like interface for performing an action with a Result that, if there are no errors is returned as-is, but
|
||||||
* if there is an error then a new error Result is returned.
|
* if there is an error then a new error Result is returned.
|
||||||
|
@ -50,7 +52,7 @@ public interface WithResultContinuation<T> {
|
||||||
try {
|
try {
|
||||||
run();
|
run();
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
return Result.error(e);
|
return Result.error(TypeReference.create(), e);
|
||||||
}
|
}
|
||||||
return currentResult;
|
return currentResult;
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,9 +20,28 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An experiment in creating something similar to a Type-Alias in Java.
|
* <h1>Result</h1>
|
||||||
*
|
*
|
||||||
* <p>Ideas initially lifted from the Design with Types series at https://fsharpforfunandprofit.com/</p>
|
* Allows handling error conditions without the need to {@code catch}
|
||||||
|
* exceptions.
|
||||||
|
*
|
||||||
|
* <p>When a {@link Result} is returned from a method, it will be in one of two
|
||||||
|
* states: {@link Success} or {@link Err}. The {@code Success} state will
|
||||||
|
* contain a value from the method. The {@code Err} state will contain a
|
||||||
|
* {@link java.lang.Throwable} detailing the reason for the failure.</p>
|
||||||
|
*
|
||||||
|
* <p>Methods returning a {@code Result} should not throw any exceptions.</p>
|
||||||
|
*
|
||||||
|
* <p>{@code Result} is a Monad.</p>
|
||||||
|
*
|
||||||
|
* <h2>Static Constructors:</h2>
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link Result#ok()}</li>
|
||||||
|
* <li>{@link Result#ok(Object)}</li>
|
||||||
|
* <li>{@link Result#of(Callable)}</li>
|
||||||
|
* <li>{@link Result#ofVoid(VoidCallable)}</li>
|
||||||
|
* <li>{@link Result#error(Throwable)}</li>
|
||||||
|
* </ul>
|
||||||
*
|
*
|
||||||
* @author Paul Campbell (pcampbell@kemitix.net)
|
* @author Paul Campbell (pcampbell@kemitix.net)
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -114,11 +114,17 @@ class MaybeTest implements WithAssertions {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void fromOptional() {
|
void mapFromOptional() {
|
||||||
assertThat(Optional.of(1).map(Maybe::just).orElseGet(Maybe::nothing)).isEqualTo(just(1));
|
assertThat(Optional.of(1).map(Maybe::just).orElseGet(Maybe::nothing)).isEqualTo(just(1));
|
||||||
assertThat(Optional.empty().map(Maybe::just).orElseGet(Maybe::nothing)).isEqualTo(nothing());
|
assertThat(Optional.empty().map(Maybe::just).orElseGet(Maybe::nothing)).isEqualTo(nothing());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void fromOptional() {
|
||||||
|
assertThat(Maybe.fromOptional(Optional.of(1))).isEqualTo(just(1));
|
||||||
|
assertThat(Maybe.fromOptional(Optional.empty())).isEqualTo(nothing());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void peek() {
|
void peek() {
|
||||||
final AtomicInteger ref = new AtomicInteger(0);
|
final AtomicInteger ref = new AtomicInteger(0);
|
||||||
|
|
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue