From e2511f389ddf1b3a47b4f3e6215705f9fe009fae Mon Sep 17 00:00:00 2001 From: JoseLion Date: Sun, 12 Nov 2023 03:41:44 -0500 Subject: [PATCH] feat(core): Refactor API --- README.md | 79 ++-- .../joselion/maybe/CloseableHandler.java | 193 +++++++++ .../github/joselion/maybe/EffectHandler.java | 47 ++- .../java/io/github/joselion/maybe/Maybe.java | 371 ++++++++++++++---- .../github/joselion/maybe/ResourceHolder.java | 145 ------- ...{ResolveHandler.java => SolveHandler.java} | 285 +++++++++----- .../io/github/joselion/maybe/Maybe.java | 371 ++++++++++++++---- ...rTests.java => CloseableHandlerTests.java} | 65 +-- .../joselion/maybe/EffectHandlerTests.java | 60 +-- .../io/github/joselion/maybe/MaybeTests.java | 366 +++++++++++------ ...ndlerTests.java => SolveHandlerTests.java} | 170 ++++---- 11 files changed, 1451 insertions(+), 701 deletions(-) create mode 100644 src/main/java/io/github/joselion/maybe/CloseableHandler.java delete mode 100644 src/main/java/io/github/joselion/maybe/ResourceHolder.java rename src/main/java/io/github/joselion/maybe/{ResolveHandler.java => SolveHandler.java} (52%) rename src/test/java/io/github/joselion/maybe/{ResourceHolderTests.java => CloseableHandlerTests.java} (74%) rename src/test/java/io/github/joselion/maybe/{ResolveHandlerTests.java => SolveHandlerTests.java} (78%) diff --git a/README.md b/README.md index c8373a2..0aab2c2 100644 --- a/README.md +++ b/README.md @@ -37,8 +37,8 @@ For example, the JDK17+ version of `Either` uses a combination of [sealed ## Breaking Changes (from v2 to v3) - Due to changes on GitHub policies (and by consequence on Maven), it's no longer allowed to use `com.github` as a valid group ID prefix. To honor that and maintain consistency, **as of v3**, the artifact ID was renamed to `io.github.joselion.maybe`. If you want to use a version **before v3**, you can still find it using `com.github.joselion:maybe` artifact. -- A `ResolveHandler` can no longer be empty. It either has the resolved value or an error. -- The method `ResolveHandler#filter` was removed to avoid the posibility of an inconsitent empty handler. +- A `SolveHandler` can no longer be empty. It either has the solved value or an error. +- The method `SolveHandler#filter` was removed to avoid the posibility of an inconsitent empty handler. - The `WrapperException` type was removed. Errors now propagate downstream with the API. - The method `EffectHandler#toMaybe` was removed as it didn't make sense for effects. - All `*Checked.java` functions were renamed to `Throwing*.java` @@ -69,77 +69,78 @@ implementation('io.github.joselion:maybe:x.y.z') ## Basics We'd use `Maybe` for 3 different cases: -- **Resolve:** When we need to obtain a value from a throwing operation. -- **Effects:** When we need to run an effect from a throwing operation, so no value is returned. -- **Auto-closeables:** When we need to handle resources that need to closed (as in `try-with-resource` blocks) +- **Solve:** When we need to obtain a value from a throwing operation. +- **Effects:** When we need to run effects that may throw exception(s), so no value is returned. +- **Closeables:** When we need to use a closeable resources on another operation (as in `try-with-resource` blocks) -We can create simple instances of Maybe using `Maybe.just(value)` or `Maybe.nothing()` so we can chain throwing operations to it that will create the **handlers**. We also provide the convenience static methods `.fromResolver(..)` and `.fromEffect(..)` to create **handlers** directly from lambda expressions. Given the built-in lambda expression do not allow checked exception, we provide a few basic functional interfaces like `ThrowingFunction`, that are just like the built-in ones, but with a `throws E` declaration. You can find them all in the [util packages][util-package-ref] of the library. +We can create simple instances of Maybe using `Maybe.of(value)` or `Maybe.empty()` so we can chain throwing operations to it that will create the **handlers**. We also provide the convenience static methods `.from(ThrowingSupplier)` and `.from(ThrowingRunnable)` to create **handlers** directly from lambda expressions. Given the built-in lambda expression do not allow checked exception, we provide a few basic functional interfaces like `ThrowingFunction`, that are just like the built-in ones, but with a `throws E` declaration. You can find them all in the [util packages][util-package-ref] of the library. -### Resolve handler +### Solve handler -Once a resolver operation runs we'll get a [ResolveHandler][resolve-handler-ref] instance. This is the API that lets you handle the possible exception and produce a final value, or chain more operations to it. +Once a solver operation runs we'll get a [SolveHandler][solve-handler-ref] instance. This is the API that lets you handle the possible exception and produce a final value, or chain more operations to it. ```java final Path path = Paths.get("foo.txt"); -final List fooLines = Maybe.fromResolver(() -> Files.readAllLines(path)) +final List fooLines = Maybe.from(() -> Files.readAllLines(path)) .doOnError(error -> log.error("Fail to read the file", error)) // where `error` has type IOException .orElse(List.of()); // or we could use method reference -final List fooLines = Maybe.just(path) - .resolver(Files::readAllLines) +final List fooLines = Maybe.of(path) + .solve(Files::readAllLines) .doOnError(error -> log.error("Fail to read the file", error)) // where `error` has type IOException .orElseGet(List::of); // the else value is lazy now ``` -The method `.readAllLines(..)` on example above reads from a file, which may throw a `IOException`. With the resolver API we can run an effect if the exception was thrown. The we use `.orElse(..)` to safely unwrap the resulting value or another one in case of failure. +The method `.readAllLines(..)` on example above reads from a file, which may throw a `IOException`. With the solver API we can run an effect if the exception was thrown. The we use `.orElse(..)` to safely unwrap the resulting value or another one in case of failure. ### Effect handler -When an effect operation runs we'll get a [EffectHandler][effect-handler-ref] instences. Likewise, this is the API to handle any possinble exception the effect may throw. This handler is very similar to the [ResolveHandler][resolve-handler-ref], but given an effect will never resolve a value, it does not have any of the methods related to manipulating or unwrapping the value. +When an effect operation runs we'll get a [EffectHandler][effect-handler-ref] instences. Likewise, this is the API to handle any possinble exception the effect may throw. This handler is very similar to the [SolveHandler][solve-handler-ref], but given an effect will never solve a value, it does not have any of the methods related to manipulating or unwrapping the value. ```java -Maybe.fromEffect(() -> { - final String to = ... - final String from = ... - final String message = ... - - MailService.send(message, to, from); -}) -.doOnError(error -> { // the `error` has type `MessagingException` - MailService.report(error.getMessage()); -}); +Maybe + .from(() -> { + final String to = ... + final String from = ... + final String message = ... + + MailService.send(message, to, from); + }) + .doOnError(error -> { // the `error` has type `MessagingException` + MailService.report(error.getMessage()); + }); ``` In the example above the `.send(..)` methods may throw a `MessagingException`. With the effect API we handle the error running another effect, i.e. reporting the error to another service. ### Auto-closeable resource -Maybe also offers a way to work with [AutoCloseable](https://docs.oracle.com/javase/8/docs/api/java/lang/AutoCloseable.html) resources in a similar way the `try-with-resource` statement does, but with a more functional approach. We do this by creating a [ResourceHolder][resource-holder-ref] instance from an autoclosable value, which will hold on to the value to close it at the end. The resource API lets you resolve or run effects using the resource, so we can ultimately handle the throwing operation with either the [ResolveHandler][resolve-handler-ref] or the [EffectHandler][effect-handler-ref]. +Maybe also offers a way to work with [AutoCloseable](https://docs.oracle.com/javase/8/docs/api/java/lang/AutoCloseable.html) resources in a similar way the `try-with-resource` statement does, but with a more functional approach. We do this by creating a [CloseableHandler][closeable-handler-ref] instance from an autoclosable value, which will hold on to the value to close it at the end. The resource API lets you solve values or run effects using a closable resource, so we can ultimately handle the throwing operation with either the [SolveHandler][solve-handler-ref] or the [EffectHandler][effect-handler-ref]. ```java Maybe.withResource(myResource) - .resolveClosing(res -> { + .solve(res -> { // Return something using `res` }); Maybe.withResource(myResource) - .runEffectClosing(res -> { + .effect(res -> { // do somthing with `res` }); ``` -In many cases, the resource you need will also throw an exception when we obtain it. We encourage you to first handle the exception that obtaining the resource may throw, and then map the value to a [ResourceHolder][resource-holder-ref] to handle the next operation. For this [ResolveHandler][resolve-handler-ref] provides a `.mapToResource(..)` method so you can map resolved values to resources. +In many cases, the resource you need will also throw an exception when we obtain it. We encourage you to first handle the exception that obtaining the resource may throw, and then map the value to a [CloseableHandler][closeable-handler-ref] to handle the next operation. For this [SolveHandler][solve-handler-ref] provides a `.mapToResource(..)` method so you can map solved values to resources. ```java public Properties parsePropertiesFile(final String filePath) { - return Maybe.just(filePath) - .resolve(FileInputStream::new) + return Maybe.of(filePath) + .solve(FileInputStream::new) .catchError(err -> /* Handle the error */) .mapToResource(Function.identity()) - .resolveClosing(inputStream -> { + .solve(inputStream -> { final Properties props = new Properties(); props.load(inputStream); @@ -149,15 +150,15 @@ public Properties parsePropertiesFile(final String filePath) { } ``` -> We know the first resolved value extends from `AutoCloseable`, but the compiler doesn't. We need to explicitly map the value with `Function.identity()` so the compiler can safely ensure that the resource can be closed. +> We know the first solved value extends from `AutoCloseable`, but the compiler doesn't. We need to explicitly map the value with `Function.identity()` so the compiler can safely ensure that the resource can be closed. ### Catching multiple exceptions Some operation may throw multiple type of exceptions. We can choose how to handle each one using one of the `.catchError(..)` matcher overloads. This method can be chained one after another, meaning the first one to match the exception type will handle the error. However, the compiler cannot ensure exhaustive matching of the error types (for now), so we'll always need to handle a default case with a terminal operator. ```java -Maybe.just(path) - .resolve(Files::readAllLines) // throws IOException +Maybe.of(path) + .solve(Files::readAllLines) // throws IOException .catchError(FileNotFoundException.class, err -> ...) .catchError(FileSystemException.class, err -> ...) .catchError(EOFException.class, err -> ...) @@ -172,7 +173,7 @@ An awesome extra of `Maybe`, is that it provides a useful [Either][either- 2. There only exist 2 implementations of `Either`: `Either.Left` and `Either.Right`. In those implementations, only one field is used to store the instance value. 3. It's not possible to create an `Either` instance of a `null` value. -The `Either` makes a lot of sense when resolving values from throwing operations. At the end of the day, you can end up with either the resolved value (`Rigth`) or the thrown exception (`Left`). You can convert from a `ResolveHandler` to an `Either` usong the `ResolveHandler#toEither` terminal operator. +The `Either` makes a lot of sense when resolving values from throwing operations. At the end of the day, you can end up with either the solved value (`Rigth`) or the thrown exception (`Left`). You can convert from a `SolveHandler` to an `Either` usong the `SolveHandler#toEither` terminal operator. To use `Either` on its own, use the factory methods to create an instance and the API to handle/unwrap the value: @@ -206,13 +207,13 @@ Take a look at the [documentation][either-ref] to see all the methods available ## Optional interoperability -The API provides full interoperability with Java's `Optional`. You can use `Maybe.fromOptional(..)` to create an instance from an optional value, or you can use the terminal operator `.toOptional()` to unwrap the value to an optional too. However, there's a change you might want to create a `Maybe` withing the Optional API or another library like [Project Reactor](https://projectreactor.io/), like from `.map(..)` method. To make this esier the API provides overloads to that create partial applications, and when fully applied return the specific handler. +The API provides full interoperability with Java's `Optional`. You can use `Maybe.from(Optional)` overload to create an instance from an optional value, or you can use the terminal operator `.toOptional()` to unwrap the value to an optional too. However, there's a change you might want to create a `Maybe` withing the Optional API or another library like [Project Reactor](https://projectreactor.io/), like from a `.map(..)` method. To make this esier the API provides overloads to that create partial applications, and when fully applied return the specific handler. So instead of having nested lambdas like this: ```java Optional.ofNullable(rawValue) - .map(str -> Maybe.fromResolver(() -> Base64.getDecoder().decode(str))) + .map(str -> Maybe.from(() -> Base64.getDecoder().decode(str))) .map(decoded -> decoded.catchError(...)); ``` @@ -220,7 +221,7 @@ You can use the partial application overload and use method reference syntax: ```java Optional.ofNullable(rawValue) - .map(Maybe.partialResolver(Base64.getDecoder()::decode)) + .map(Maybe.partial(Base64.getDecoder()::decode)) .map(decoded -> decoded.catchError(...)); ``` @@ -245,8 +246,8 @@ Contributions are very welcome! To do so, please fork this repository and open a [Apache License 2.0](https://github.com/JoseLion/maybe/blob/main/LICENSE) -[resolve-handler-ref]: https://javadoc.io/doc/io.github.joselion/maybe/latest/com/github/joselion/maybe/ResolveHandler.html +[solve-handler-ref]: https://javadoc.io/doc/io.github.joselion/maybe/latest/com/github/joselion/maybe/SolveHandler.html [effect-handler-ref]: https://javadoc.io/doc/io.github.joselion/maybe/latest/com/github/joselion/maybe/EffectHandler.html -[resource-holder-ref]: https://javadoc.io/doc/io.github.joselion/maybe/latest/com/github/joselion/maybe/ResourceHolder.html +[closeable-handler-ref]: https://javadoc.io/doc/io.github.joselion/maybe/latest/com/github/joselion/maybe/CloseableHandler.html [util-package-ref]: https://javadoc.io/doc/io.github.joselion/maybe/latest/com/github/joselion/maybe/util/package-summary.html [either-ref]: https://javadoc.io/doc/io.github.joselion/maybe/latest/com/github/joselion/maybe/Either.html diff --git a/src/main/java/io/github/joselion/maybe/CloseableHandler.java b/src/main/java/io/github/joselion/maybe/CloseableHandler.java new file mode 100644 index 0000000..ef9d195 --- /dev/null +++ b/src/main/java/io/github/joselion/maybe/CloseableHandler.java @@ -0,0 +1,193 @@ +package io.github.joselion.maybe; + +import java.util.Optional; + +import io.github.joselion.maybe.helpers.Commons; +import io.github.joselion.maybe.util.Either; +import io.github.joselion.maybe.util.function.ThrowingConsumer; +import io.github.joselion.maybe.util.function.ThrowingFunction; + +/** + * CloseableHandler is an API that allows to solve or run an effect using an + * {@link AutoCloseable} resource. This resource will be automatically closed + * after the {@code solve} or the {@code effect} operation is finished. + * + * @param The autoclosable type + * @param The throwable type + * + * @author Jose Luis Leon + * @since v1.3.0 + */ +public class CloseableHandler { + + private final Either value; + + private CloseableHandler(final T resource) { + this.value = Either.ofRight(resource); + } + + private CloseableHandler(final E error) { + this.value = Either.ofLeft(error); + } + + /** + * Internal use method to instatiate a CloseableHandler from a given resource. + * + * @param the type of the resource + * @param the type of the error + * @param resource the resource to instantiate the CloseableHandler with + * @return a new instance of CloseableHandler with the given resource + */ + static CloseableHandler from(final T resource) { + return new CloseableHandler<>(resource); + } + + /** + * Internal use method to instatiate a failed CloseableHandler from an exception. + * + * @param the type of the resource + * @param the type of the error + * @param error the error to instantiate the failed CloseableHandler + * @return a new instance of the failed CloseableHandler with the error + */ + static CloseableHandler failure(final E error) { + return new CloseableHandler<>(error); + } + + /** + * Internal use only. + * + * @return the possible stored resource + */ + Optional resource() { + return this.value.rightToOptional(); + } + + /** + * Internal use only. + * + * @return the possible propagated error + */ + Optional error() { + return this.value.leftToOptional(); + } + + /** + * If the resource is present, solves the value of a throwing operation + * using a {@link ThrowingFunction} expression which has the previously + * prepared resource in the argument. The resource is automatically closed + * after the operation finishes, just like a common try-with-resources + * statement. + *

+ * Returs a {@link SolveHandler} which allows to handle the possible error + * and return a safe value. The returned handler is {@code empty} if neither + * the resource nor the error is present. + * + * @param the type of the value returned by the {@code solver} + * @param the type of exception the {@code solver} may throw + * @param solver the checked function operation to solve + * @return a {@link SolveHandler} with either the value solved or the thrown + * exception to be handled + */ + public SolveHandler solve( + final ThrowingFunction solver + ) { + return this.value + .mapLeft(Commons::cast) + .unwrap( + SolveHandler::ofError, + resource -> { + try (var res = resource) { + return SolveHandler.ofSuccess(solver.apply(res)); + } catch (final Throwable e) { //NOSONAR + final var error = Commons.cast(e); + return SolveHandler.ofError(error); + } + } + ); + } + + /** + * If the resource is present, solves the value of a throwing operation + * using a {@link ThrowingFunction} expression which has the previously + * prepared resource in the argument. The resource is automatically closed + * after the operation finishes, just like a common try-with-resources + * statement. + *

+ * Returs a {@link SolveHandler} which allows to handle the possible error + * and return a safe value. The returned handler is {@code empty} if neither + * the resource nor the error is present. + * + * @param the type of the value returned by the {@code solver} + * @param the type of exception the {@code solver} may throw + * @param solver the checked function operation to solve + * @return a {@link SolveHandler} with either the value solved or the thrown + * exception to be handled + * @deprecated in favor of {@link #solve(ThrowingFunction)} + */ + @Deprecated(forRemoval = true, since = "3.4.0") + public SolveHandler resolveClosing(// NOSONAR + final ThrowingFunction solver + ) { + return this.solve(solver); + } + + /** + * If the resource is present, runs an effect that may throw an exception + * using a {@link ThrowingConsumer} expression which has the previously + * prepared resource in the argument. The resource is automatically closed + * after the operation finishes, just like a common try-with-resources + * statement. + *

+ * Returning then an {@link EffectHandler} which allows to handle the + * possible error. The returned handler is {@code empty} if neither the + * resource nor the error is present. + * + * @param the type of exception the {@code effect} may throw + * @param effect the checked consumer operation to execute + * @return an {@link EffectHandler} with either the thrown exception to be + * handled or empty + */ + public EffectHandler effect( + final ThrowingConsumer effect + ) { + return this.value + .mapLeft(Commons::cast) + .unwrap( + EffectHandler::ofError, + resource -> { + try (var res = resource) { + effect.accept(res); + return EffectHandler.empty(); + } catch (final Throwable e) { // NOSONAR + final var error = Commons.cast(e); + return EffectHandler.ofError(error); + } + } + ); + } + + /** + * If the resource is present, runs an effect that may throw an exception + * using a {@link ThrowingConsumer} expression which has the previously + * prepared resource in the argument. The resource is automatically closed + * after the operation finishes, just like a common try-with-resources + * statement. + *

+ * Returning then an {@link EffectHandler} which allows to handle the + * possible error. The returned handler is {@code empty} if neither the + * resource nor the error is present. + * + * @param the type of exception the {@code effect} may throw + * @param effect the checked consumer operation to execute + * @return an {@link EffectHandler} with either the thrown exception to be + * handled or empty + * @deprecated in favor of {@link #effect(ThrowingConsumer)} + */ + @Deprecated(forRemoval = true, since = "3.4.0") + public EffectHandler runEffectClosing(// NOSONAR + final ThrowingConsumer effect + ) { + return this.effect(effect); + } +} diff --git a/src/main/java/io/github/joselion/maybe/EffectHandler.java b/src/main/java/io/github/joselion/maybe/EffectHandler.java index 593d9fa..d745cc8 100644 --- a/src/main/java/io/github/joselion/maybe/EffectHandler.java +++ b/src/main/java/io/github/joselion/maybe/EffectHandler.java @@ -156,13 +156,46 @@ public EffectHandler catchError(final Consumer handler) { * @return a new {@link EffectHandler} representing the result of one of the * invoked callback */ - public EffectHandler runEffect( + public EffectHandler effect( final ThrowingRunnable onSuccess, final ThrowingConsumer onError ) { return this.error - .map(Maybe.partialEffect(onError)) - .orElseGet(() -> Maybe.fromEffect(onSuccess)); + .map(Maybe.partial(onError)) + .orElseGet(() -> Maybe.from(onSuccess)); + } + + /** + * Chain another effect covering both cases of success or error of the + * previous effect in two different callbacks. + * + * @param the type of the error the new effect may throw + * @param onSuccess a runnable checked function to run in case of succeess + * @param onError a runnable checked function to run in case of error + * @return a new {@link EffectHandler} representing the result of one of the + * invoked callback + * @deprecated in favor of {@link #effect(ThrowingRunnable, ThrowingConsumer)} + */ + @Deprecated(forRemoval = true, since = "3.4.0") + public EffectHandler runEffect(// NOSONAR + final ThrowingRunnable onSuccess, + final ThrowingConsumer onError + ) { + return this.effect(onSuccess, onError); + } + + /** + * Chain another effect if the previous completed with no error. Otherwise, + * ignores the current error and return a new {@link EffectHandler} that is + * either empty or has a different error cause by the next effect. + * + * @param the type of the error the new effect may throw + * @param effect a runnable checked function to run in case of succeess + * @return a new {@link EffectHandler} that is either empty or with the + * thrown error + */ + public EffectHandler effect(final ThrowingRunnable effect) { + return this.effect(effect, err -> { }); } /** @@ -174,9 +207,13 @@ public EffectHandler runEffect( * @param effect a runnable checked function to run in case of succeess * @return a new {@link EffectHandler} that is either empty or with the * thrown error + * @deprecated in favor of {@link #effect(ThrowingRunnable)} */ - public EffectHandler runEffect(final ThrowingRunnable effect) { - return this.runEffect(effect, err -> { }); + @Deprecated(forRemoval = true, since = "3.4.0") + public EffectHandler runEffect(// NOSONAR + final ThrowingRunnable effect + ) { + return this.effect(effect); } /** diff --git a/src/main/java/io/github/joselion/maybe/Maybe.java b/src/main/java/io/github/joselion/maybe/Maybe.java index 4f022cf..d0e6d2e 100644 --- a/src/main/java/io/github/joselion/maybe/Maybe.java +++ b/src/main/java/io/github/joselion/maybe/Maybe.java @@ -42,72 +42,147 @@ Optional value() { /** * Creates a {@link Maybe} wrapper of the given value. If the value is - * {@code null}, it returns a {@link #nothing()}. + * {@code null}, it returns a {@link #empty()}. * * @param the type of the value * @param value the value be wrapped * @return a {@code Maybe} wrapping the value if it's non-{@code null}, - * {@link #nothing()} otherwise + * {@link #empty()} otherwise */ - public static Maybe just(final T value) { + public static Maybe of(final @Nullable T value) { return new Maybe<>(value); } /** - * Creates a {@link Maybe} wrapper with nothing on it. This means the wrapper - * does not contains a value because an exception may have occurred. - * + * Creates a {@link Maybe} wrapper of the given optional if not empty. + * Returns {@link #empty()} if the optional is empty or {@code null}. + * + * @apiNote + * It is not convenient to create a {@code Maybe} wrapping an + * {@code Optional}. It'll be hard to use the value later on, and it defies + * the purpose of using {@code Maybe} in the first place (Maybe is like + * Optional, but for handling exceptions). But if you really want to do that + * for some reason, here are some workarounds: + *

+   *  Maybe.of(value).map(Optional::of);
+   *       // ^ can be an `Optional<T>` or not
+   * 
+ * * @param the type of the value - * @return a {@code Maybe} with nothing + * @param value an optional value to create the wrapper from + * @return a {@code Maybe} wrapping the value if it's not empty. + * {@link #empty()} otherwise */ - public static Maybe nothing() { + public static Maybe of(final @Nullable Optional value) { // NOSONAR + if (value != null) { // NOSONAR + return value + .map(Maybe::new) + .orElseGet(Maybe::empty); + } + return new Maybe<>(null); } + /** + * Creates a {@link Maybe} wrapper of the given value. If the value is + * {@code null}, it returns a {@link #empty()}. + * + * @param the type of the value + * @param value the value be wrapped + * @return a {@code Maybe} wrapping the value if it's non-{@code null}, + * {@link #empty()} otherwise + * @deprecated in favor of {@link #of(Object)} + */ + @Deprecated(forRemoval = true, since = "3.4.0") + public static Maybe just(final @Nullable T value) { // NOSONAR + return Maybe.of(value); + } + + /** + * Creates an empty {@link Maybe} instance. + * + * @param the type of the value + * @return an empty {@code Maybe} + */ + public static Maybe empty() { + return Maybe.of(null); + } + + /** + * Creates an empty {@link Maybe} instance. + * + * @param the type of the value + * @return an empty {@code Maybe} + * @deprecated in favor of {@link #empty()} + */ + @Deprecated(forRemoval = true, since = "3.4.0") + public static Maybe nothing() { // NOSONAR + return Maybe.empty(); + } + /** * Creates a {@link Maybe} wrapper of the given value if the optional is not - * empty. Returns a {@link #nothing()} otherwise. + * empty. Returns a {@link #empty()} otherwise. *

* This is a convenience creator that would be equivalent to: *

-   *  Maybe.just(opt)
-   *    .resolve(Optional::get)
+   *  Maybe.of(opt)
+   *    .solve(Optional::get)
    *    .toMaybe();
    * 
* * @param the type of the value * @param value an optional value to create the wrapper from * @return a {@code Maybe} wrapping the value if it's not empty. - * {@link #nothing()} otherwise + * {@link #empty()} otherwise + * @deprecated in favor of {@link #of(Optional)} */ - public static Maybe fromOptional(final Optional value) { - return value - .map(Maybe::new) - .orElseGet(Maybe::nothing); + @Deprecated(forRemoval = true, since = "3.4.0") + public static Maybe fromOptional(final Optional value) { // NOSONAR + return Maybe.of(value); } /** - * Resolves the value of a throwing operation using a {@link ThrowingSupplier} - * expression. Returning then a {@link ResolveHandler} which allows to handle + * Solves the value of a throwing operation using a {@link ThrowingSupplier} + * expression. Returning then a {@link SolveHandler} which allows to handle * the possible error and return a safe value. * - * @param the type of the value returned by the {@code resolver} - * @param the type of exception the {@code resolver} may throw - * @param resolver the checked supplier operation to resolve - * @return a {@link ResolveHandler} with either the value resolved or the thrown + * @param the type of the value returned by the {@code solver} + * @param the type of exception the {@code solver} may throw + * @param solver the checked supplier operation to solve + * @return a {@link SolveHandler} with either the value solved or the thrown * exception to be handled */ - public static ResolveHandler fromResolver( - final ThrowingSupplier resolver + public static SolveHandler from( + final ThrowingSupplier solver ) { try { - return ResolveHandler.ofSuccess(resolver.get()); + return SolveHandler.ofSuccess(solver.get()); } catch (Throwable e) { // NOSONAR final var error = Commons.cast(e); - return ResolveHandler.ofError(error); + return SolveHandler.ofError(error); } } + /** + * Solves the value of a throwing operation using a {@link ThrowingSupplier} + * expression. Returning then a {@link SolveHandler} which allows to handle + * the possible error and return a safe value. + * + * @param the type of the value returned by the {@code solver} + * @param the type of exception the {@code solver} may throw + * @param solver the checked supplier operation to solve + * @return a {@link SolveHandler} with either the value solved or the thrown + * exception to be handled + * @deprecated in favor of {@link #from(ThrowingSupplier)} + */ + @Deprecated(forRemoval = true, since = "3.4.0") + public static SolveHandler fromResolver(// NOSONAR + final ThrowingSupplier solver + ) { + return Maybe.from(solver); + } + /** * Runs an effect that may throw an exception using a {@link ThrowingRunnable} * expression. Returning then an {@link EffectHandler} which allows to handle @@ -116,9 +191,9 @@ public static ResolveHandler fromResolver( * @param the type of exception the {@code effect} may throw * @param effect the checked runnable operation to execute * @return an {@link EffectHandler} with either the thrown exception to be - * handled or nothing + * handled or empty */ - public static EffectHandler fromEffect(final ThrowingRunnable effect) { + public static EffectHandler from(final ThrowingRunnable effect) { try { effect.run(); return EffectHandler.empty(); @@ -129,16 +204,66 @@ public static EffectHandler fromEffect(final ThrowingRu } /** - * Convenience partial application of a {@code resolver}. This method creates + * Runs an effect that may throw an exception using a {@link ThrowingRunnable} + * expression. Returning then an {@link EffectHandler} which allows to handle + * the possible error. + * + * @param the type of exception the {@code effect} may throw + * @param effect the checked runnable operation to execute + * @return an {@link EffectHandler} with either the thrown exception to be + * handled or empty + * @deprecated in favor of {@link #from(ThrowingRunnable)} + */ + @Deprecated(forRemoval = true, since = "3.4.0") + public static EffectHandler fromEffect(// NOSONAR + final ThrowingRunnable effect + ) { + return Maybe.from(effect); + } + + /** + * Convenience partial application of a {@code solver}. This method creates * a function that receives an {@code S} value which can be used to produce a - * {@link ResolveHandler} once applied. This is specially useful when we want + * {@link SolveHandler} once applied. This is specially useful when we want * to create a {@link Maybe} from a callback argument, like on a * {@link Optional#map(Function)} for instance. *

* In other words, the following code *

    *  Optional.of(value)
-   *    .map(str -> Maybe.fromResolver(() -> decode(str)));
+   *    .map(str -> Maybe.from(() -> decode(str)));
+   * 
+ * Is equivalent to + *
+   *  Optional.of(value)
+   *    .map(Maybe.partial(this::decode));
+   * 
+ * + * @param the type of the value the returned function receives + * @param the type of the value to be solved + * @param the type of the error the solver may throw + * @param solver a checked function that receives an {@code S} value and + * returns a {@code T} value + * @return a partially applied {@link SolveHandler}. This means, a function + * that receives an {@code S} value, and produces a {@code SolveHandler} + */ + public static Function> partial( + final ThrowingFunction solver + ) { + return value -> Maybe.from(() -> solver.apply(value)); + } + + /** + * Convenience partial application of a {@code solver}. This method creates + * a function that receives an {@code S} value which can be used to produce a + * {@link SolveHandler} once applied. This is specially useful when we want + * to create a {@link Maybe} from a callback argument, like on a + * {@link Optional#map(Function)} for instance. + *

+ * In other words, the following code + *

+   *  Optional.of(value)
+   *    .map(str -> Maybe.from(() -> decode(str)));
    * 
* Is equivalent to *
@@ -147,17 +272,19 @@ public static  EffectHandler fromEffect(final ThrowingRu
    * 
* * @param the type of the value the returned function receives - * @param the type of the value to be resolved - * @param the type of the error the resolver may throw - * @param resolver a checked function that receives an {@code S} value and + * @param the type of the value to be solved + * @param the type of the error the solver may throw + * @param solver a checked function that receives an {@code S} value and * returns a {@code T} value - * @return a partially applied {@link ResolveHandler}. This means, a function - * that receives an {@code S} value, and produces a {@code ResolveHandler} + * @return a partially applied {@link SolveHandler}. This means, a function + * that receives an {@code S} value, and produces a {@code SolveHandler} + * @deprecated in favor of {@link #partial(ThrowingFunction)} */ - public static Function> partialResolver( - final ThrowingFunction resolver + @Deprecated(forRemoval = true, since = "3.4.0") + public static Function> partialResolver(// NOSONAR + final ThrowingFunction solver ) { - return value -> Maybe.fromResolver(() -> resolver.apply(value)); + return Maybe.partial(solver); } /** @@ -170,7 +297,37 @@ public static Function> part * In other words, the following code *
    *  Optional.of(value)
-   *    .map(msg -> Maybe.fromEffect(() -> sendMessage(msg)));
+   *    .map(msg -> Maybe.from(() -> sendMessage(msg)));
+   * 
+ * Is equivalent to + *
+   *  Optional.of(value)
+   *    .map(Maybe.partial(this::sendMessage));
+   * 
+ * + * @param the type of the value the returned function receives + * @param the type of the error the effect may throw + * @param effect a checked consumer that receives an {@code S} value + * @return a partially applied {@link EffectHandler}. This means, a function + * that receives an {@code S} value, and produces an {@code EffectHandler} + */ + public static Function> partial( + final ThrowingConsumer effect + ) { + return value -> Maybe.from(() -> effect.accept(value)); + } + + /** + * Convenience partial application of an {@code effect}. This method creates + * a function that receives an {@code S} value which can be used to produce + * an {@link EffectHandler} once applied. This is specially useful when we + * want to create a {@link Maybe} from a callback argument, like on a + * {@link Optional#map(Function)} for instance. + *

+ * In other words, the following code + *

+   *  Optional.of(value)
+   *    .map(msg -> Maybe.from(() -> sendMessage(msg)));
    * 
* Is equivalent to *
@@ -179,73 +336,75 @@ public static  Function> part
    * 
* * @param the type of the value the returned function receives - * @param the type of the error the resolver may throw + * @param the type of the error the effect may throw * @param effect a checked consumer that receives an {@code S} value * @return a partially applied {@link EffectHandler}. This means, a function * that receives an {@code S} value, and produces an {@code EffectHandler} + * @deprecated in favor of {@link #partial(ThrowingConsumer)} */ - public static Function> partialEffect( + @Deprecated(forRemoval = true, since = "3.4.0") + public static Function> partialEffect(// NOSONAR final ThrowingConsumer effect ) { - return value -> Maybe.fromEffect(() -> effect.accept(value)); + return Maybe.partial(effect); } /** - * Prepare an {@link AutoCloseable} resource to use in a resolver or effect. + * Prepare an {@link AutoCloseable} resource to use in a solver or effect. * The resource will be automatically closed after the operation is finished, * just like a common try-with-resources statement. * * @param the type of the resource. Extends from {@link AutoCloseable} * @param the type of error the holder may have * @param resource the {@link AutoCloseable} resource to prepare - * @return a {@link ResourceHolder} which let's you choose to resolve a value + * @return a {@link CloseableHandler} which let's you choose to solve a value * or run an effect using the prepared resource */ - public static ResourceHolder withResource(final R resource) { - return ResourceHolder.from(resource); + public static CloseableHandler withResource(final R resource) { + return CloseableHandler.from(resource); } /** - * Prepare an {@link AutoCloseable} resource to use in a resolver or effect, + * Prepare an {@link AutoCloseable} resource to use in a solver or effect, * using a {@link ThrowingSupplier}. Any exception thrown by the supplier - * will be propageted to the {@link ResourceHolder}. The resource will be + * will be propageted to the {@link CloseableHandler}. The resource will be * automatically closed after the operation is finished, just like a common * try-with-resources statement. * * @param the type of the resource. Extends from {@link AutoCloseable} * @param the type of error the holder may have * @param supplier the throwing supplier o the {@link AutoCloseable} resource - * @return a {@link ResourceHolder} which let's you choose to resolve a value + * @return a {@link CloseableHandler} which let's you choose to solve a value * or run an effect using the prepared resource */ - public static ResourceHolder solveResource( + public static CloseableHandler solveResource( final ThrowingSupplier supplier ) { return Maybe - .fromResolver(supplier) - .map(ResourceHolder::from) - .orElse(ResourceHolder::failure); + .from(supplier) + .map(CloseableHandler::from) + .orElse(CloseableHandler::failure); } /** * If present, maps the value to another using the provided mapper function. - * Otherwise, ignores the mapper and returns {@link #nothing()}. + * Otherwise, ignores the mapper and returns {@link #empty()}. * * @param the type the value will be mapped to * @param mapper the mapper function * @return a {@code Maybe} with the mapped value if present, - * {@link #nothing()} otherwise + * {@link #empty()} otherwise */ public Maybe map(final Function mapper) { return Maybe - .fromOptional(this.value) - .resolve(mapper::apply) + .of(this.value) + .solve(mapper::apply) .toMaybe(); } /** * If present, maps the value to another using the provided mapper function. - * Otherwise, ignores the mapper and returns {@link #nothing()}. + * Otherwise, ignores the mapper and returns {@link #empty()}. * * This method is similar to {@link #map(Function)}, but the mapping function is * one whose result is already a {@code Maybe}, and if invoked, flatMap does not @@ -254,41 +413,61 @@ public Maybe map(final Function mapper) { * @param the type the value will be mapped to * @param mapper the mapper function * @return a {@code Maybe} with the mapped value if present, - * {@link #nothing()} otherwise + * {@link #empty()} otherwise */ public Maybe flatMap(final Function> mapper) { return Maybe - .fromOptional(this.value) - .resolve(mapper::apply) + .of(this.value) + .solve(mapper::apply) .map(Commons::>cast) - .orElseGet(Maybe::nothing); + .orElseGet(Maybe::empty); } /** - * Chain the {@code Maybe} with another resolver, if and only if the previous + * Chain the {@code Maybe} with another solver, if and only if the previous * operation was handled with no errors. The value of the previous operation * is passed as argument of the {@link ThrowingFunction}. * * @param the type of value returned by the next operation - * @param the type of exception the new resolver may throw - * @param resolver a checked function that receives the current value and - * resolves another - * @return a {@link ResolveHandler} with either the resolved value, or the + * @param the type of exception the new solver may throw + * @param solver a checked function that receives the current value and + * solves another + * @return a {@link SolveHandler} with either the solved value, or the * thrown exception to be handled */ - public ResolveHandler resolve( - final ThrowingFunction resolver + public SolveHandler solve( + final ThrowingFunction solver ) { try { return this.value - .map(Maybe.partialResolver(resolver)) + .map(Maybe.partial(solver)) .orElseThrow(); } catch (final NoSuchElementException e) { final var error = Commons.cast(e); - return ResolveHandler.ofError(error); + return SolveHandler.ofError(error); } } + /** + * Chain the {@code Maybe} with another solver, if and only if the previous + * operation was handled with no errors. The value of the previous operation + * is passed as argument of the {@link ThrowingFunction}. + * + * @param the type of value returned by the next operation + * @param the type of exception the new solver may throw + * @param solver a checked function that receives the current value and + * solves another + * @return a {@link SolveHandler} with either the solved value, or the + * thrown exception to be handled + * @deprecated in favor of {@link #solve(ThrowingFunction)} + */ + @Deprecated(forRemoval = true, since = "3.4.0") + public SolveHandler resolve(// NOSONAR + final ThrowingFunction solver + ) { + return this.solve(solver); + } + /** * Chain the {@code Maybe} with another effect, if and only if the previous * operation was handled with no errors. @@ -298,10 +477,10 @@ public ResolveHandler resolve( * @return an {@link EffectHandler} with either the thrown exception to be * handled or nothing */ - public EffectHandler runEffect(final ThrowingConsumer effect) { + public EffectHandler effect(final ThrowingConsumer effect) { try { return this.value - .map(Maybe.partialEffect(effect)) + .map(Maybe.partial(effect)) .orElseThrow(); } catch (final NoSuchElementException e) { final var error = Commons.cast(e); @@ -309,19 +488,36 @@ public EffectHandler runEffect(final ThrowingConsumer the type of exception the new effect may throw + * @param effect the checked runnable operation to execute next + * @return an {@link EffectHandler} with either the thrown exception to be + * handled or nothing + * @deprecated in favor of {@link #effect(ThrowingConsumer)} + */ + @Deprecated(forRemoval = true, since = "3.4.0") + public EffectHandler runEffect(// NOSONAR + final ThrowingConsumer effect + ) { + return this.effect(effect); + } + /** * If the value is present, cast the value to another type. In case of an - * exception during the cast, a Maybe with {@link #nothing()} is returned. + * exception during the cast, a Maybe with {@link #empty()} is returned. * * @param the type that the value will be cast to * @param type the class instance of the type to cast * @return a new {@code Maybe} with the cast value if it can be cast, - * {@link #nothing()} otherwise + * {@link #empty()} otherwise */ public Maybe cast(final Class type) { return Maybe - .fromOptional(this.value) - .resolve(type::cast) + .of(this.value) + .solve(type::cast) .toMaybe(); } @@ -335,14 +531,25 @@ public boolean hasValue() { } /** - * Checks if the {@code Maybe} has nothing. That is, when no value is present. + * Checks if the {@code Maybe} is empty. That is, when no value is present. * - * @return true if the value is NOT present, false otherwise + * @return true if the value is not present, false otherwise */ - public boolean hasNothing() { + public boolean isEmpty() { return this.value.isEmpty(); } + /** + * Checks if the {@code Maybe} is empty. That is, when no value is present. + * + * @return true if the value is not present, false otherwise + * @deprecated in favor of {@link #isEmpty()} + */ + @Deprecated(forRemoval = true, since = "3.4.0") + public boolean hasNothing() { // NOSONAR + return this.isEmpty(); + } + /** * Safely unbox the value as an {@link Optional} which may or may not contain * a value. @@ -402,6 +609,6 @@ public String toString() { return this.value .map(Object::toString) .map(x -> String.format("Maybe[%s]", x)) - .orElse("Maybe.nothing"); + .orElse("Maybe.empty"); } } diff --git a/src/main/java/io/github/joselion/maybe/ResourceHolder.java b/src/main/java/io/github/joselion/maybe/ResourceHolder.java deleted file mode 100644 index f93b198..0000000 --- a/src/main/java/io/github/joselion/maybe/ResourceHolder.java +++ /dev/null @@ -1,145 +0,0 @@ -package io.github.joselion.maybe; - -import java.util.Optional; - -import io.github.joselion.maybe.helpers.Commons; -import io.github.joselion.maybe.util.Either; -import io.github.joselion.maybe.util.function.ThrowingConsumer; -import io.github.joselion.maybe.util.function.ThrowingFunction; - -/** - * ResourceHolder is a "middle step" API that allows to resolve or run an effect - * using a previously passed {@link AutoCloseable} resource. This resource will - * be automatically closed after the {@code resolve} or the {@code effect} - * operation is finished. - * - * @param The autoclosable type - * @param The throwable type - * - * @author Jose Luis Leon - * @since v1.3.0 - */ -public class ResourceHolder { - - private final Either value; - - private ResourceHolder(final R resource) { - this.value = Either.ofRight(resource); - } - - private ResourceHolder(final E error) { - this.value = Either.ofLeft(error); - } - - /** - * Internal use method to instatiate a ResourceHolder from a given resource. - * - * @param the type of the resource - * @param the type of the error - * @param resource the resource to instantiate the ResourceHolder with - * @return a new instance of ResourceHolder with the given resource - */ - static ResourceHolder from(final R resource) { - return new ResourceHolder<>(resource); - } - - /** - * Internal use method to instatiate a failed ResourceHolder from an exception. - * - * @param the type of the resource - * @param the type of the error - * @param error the error to instantiate the failed ResourceHolder - * @return a new instance of the failed ResourceHolder with the error - */ - static ResourceHolder failure(final E error) { - return new ResourceHolder<>(error); - } - - /** - * Internal use only. - * - * @return the possible stored resource - */ - Optional resource() { - return this.value.rightToOptional(); - } - - /** - * Internal use only. - * - * @return the possible propagated error - */ - Optional error() { - return this.value.leftToOptional(); - } - - /** - * If the resource is present, resolves the value of a throwing operation - * using a {@link ThrowingFunction} expression which has the previously - * prepared resource in the argument. The resource is automatically closed - * after the operation finishes, just like a common try-with-resources - * statement. - *

- * Returs a {@link ResolveHandler} which allows to handle the possible error - * and return a safe value. The returned handler has {@code nothing} if - * neither the resource nor the error is present. - * - * @param the type of the value returned by the {@code resolver} - * @param the type of exception the {@code resolver} may throw - * @param resolver the checked function operation to resolve - * @return a {@link ResolveHandler} with either the value resolved or the thrown - * exception to be handled - */ - public ResolveHandler resolveClosing( - final ThrowingFunction resolver - ) { - return this.value - .mapLeft(Commons::cast) - .unwrap( - ResolveHandler::ofError, - resource -> { - try (var res = resource) { - return ResolveHandler.ofSuccess(resolver.apply(res)); - } catch (final Throwable e) { //NOSONAR - final var error = Commons.cast(e); - return ResolveHandler.ofError(error); - } - } - ); - } - - /** - * If the resource is present, runs an effect that may throw an exception - * using a {@link ThrowingConsumer} expression which has the previously - * prepared resource in the argument. The resource is automatically closed - * after the operation finishes, just like a common try-with-resources - * statement. - *

- * Returning then an {@link EffectHandler} which allows to handle the - * possible error. The returned handler has {@code nothing} if neither the - * resource nor the error is present. - * - * @param the type of exception the {@code effect} may throw - * @param effect the checked consumer operation to execute - * @return an {@link EffectHandler} with either the thrown exception to be - * handled or nothing - */ - public EffectHandler runEffectClosing( - final ThrowingConsumer effect - ) { - return this.value - .mapLeft(Commons::cast) - .unwrap( - EffectHandler::ofError, - resource -> { - try (var res = resource) { - effect.accept(res); - return EffectHandler.empty(); - } catch (final Throwable e) { // NOSONAR - final var error = Commons.cast(e); - return EffectHandler.ofError(error); - } - } - ); - } -} diff --git a/src/main/java/io/github/joselion/maybe/ResolveHandler.java b/src/main/java/io/github/joselion/maybe/SolveHandler.java similarity index 52% rename from src/main/java/io/github/joselion/maybe/ResolveHandler.java rename to src/main/java/io/github/joselion/maybe/SolveHandler.java index c742486..dcf50a3 100644 --- a/src/main/java/io/github/joselion/maybe/ResolveHandler.java +++ b/src/main/java/io/github/joselion/maybe/SolveHandler.java @@ -13,46 +13,46 @@ import io.github.joselion.maybe.util.function.ThrowingFunction; /** - * ResolveHandler is an API to handle the possible error of a {@link Maybe}'s - * resolve operation. It can return back to maybe to continue linking operations, + * SolveHandler is an API to handle the possible error of a {@link Maybe}'s + * solve operation. It can return back to maybe to continue linking operations, * or use terminal methods to return a safe value. * * @param the type of the value passed through the {@code Maybe} - * @param the type of exception that the resolve operation may throw + * @param the type of exception that the solve operation may throw * * @author Jose Luis Leon * @since v0.3.2 */ -public final class ResolveHandler { +public final class SolveHandler { private final Either value; - private ResolveHandler(final Either value) { + private SolveHandler(final Either value) { this.value = value; } /** - * Internal use method to instantiate a ResolveHandler of a success value + * Internal use method to instantiate a SolveHandler of a success value * * @param the type of the success value * @param the type of the possible exception - * @param success the success value to instantiate the ResolveHandler - * @return a ResolveHandler instance with a success value + * @param success the success value to instantiate the SolveHandler + * @return a SolveHandler instance with a success value */ - static ResolveHandler ofSuccess(final T success) { - return new ResolveHandler<>(Either.ofRight(success)); + static SolveHandler ofSuccess(final T success) { + return new SolveHandler<>(Either.ofRight(success)); } /** - * Internal use method to instantiate a ResolveHandler of an error value + * Internal use method to instantiate a SolveHandler of an error value * * @param the type of the success value * @param the type of the possible exception - * @param error the error to instantiate the ResolveHandler - * @return a ResolveHandler instance with an error value + * @param error the error to instantiate the SolveHandler + * @return a SolveHandler instance with an error value */ - static ResolveHandler ofError(final E error) { - return new ResolveHandler<>(Either.ofLeft(error)); + static SolveHandler ofError(final E error) { + return new SolveHandler<>(Either.ofLeft(error)); } /** @@ -74,13 +74,13 @@ Optional error() { } /** - * Run an effect if the operation resolved successfully. The resolved value + * Run an effect if the operation solved successfully. The solved value * is passed in the argument of the {@code effect} consumer. * - * @param effect a function that receives the resolved value + * @param effect a function that receives the solved value * @return the same handler to continue chainning operations */ - public ResolveHandler doOnSuccess(final Consumer effect) { + public SolveHandler doOnSuccess(final Consumer effect) { this.value.doOnRight(effect); return this; @@ -96,7 +96,7 @@ public ResolveHandler doOnSuccess(final Consumer effect) { * @param effect a consumer function that receives the caught error * @return the same handler to continue chainning operations */ - public ResolveHandler doOnError(final Class ofType, final Consumer effect) { + public SolveHandler doOnError(final Class ofType, final Consumer effect) { this.value .leftToOptional() .filter(ofType::isInstance) @@ -113,7 +113,7 @@ public ResolveHandler doOnError(final Class ofTyp * @param effect a consumer function that receives the caught error * @return the same handler to continue chainning operations */ - public ResolveHandler doOnError(final Consumer effect) { + public SolveHandler doOnError(final Consumer effect) { this.value.doOnLeft(effect); return this; @@ -131,7 +131,7 @@ public ResolveHandler doOnError(final Consumer effect) { * @return a handler containing a new value if an error instance of the * provided type was caught. The same handler instance otherwise */ - public ResolveHandler catchError( + public SolveHandler catchError( final Class ofType, final Function handler ) { @@ -140,7 +140,7 @@ public ResolveHandler catchError( .filter(ofType::isInstance) .map(ofType::cast) .map(handler) - .map(ResolveHandler::ofSuccess) + .map(SolveHandler::ofSuccess) .orElse(this); } @@ -153,61 +153,105 @@ public ResolveHandler catchError( * @return a handler containing a new value if an error was caught. The same * handler instance otherwise */ - public ResolveHandler catchError(final Function handler) { + public SolveHandler catchError(final Function handler) { return this.value .mapLeft(handler) - .mapLeft(ResolveHandler::ofSuccess) + .mapLeft(SolveHandler::ofSuccess) .leftOrElse(this); } /** - * Chain another resolver covering both cases of success or error of the - * previous resolver in two different callbacks. + * Chain another solver covering both cases of success or error of the + * previous solver in two different callbacks. *

- * The first callback receives the resolved value, the second callback the - * caught error. Both should resolve another value of the same type {@code S}, + * The first callback receives the solved value, the second callback the + * caught error. Both should solve another value of the same type {@code S}, * but only one of the callbacks is invoked. It depends on whether the - * previous value was resolved or not. + * previous value was solved or not. * * @param the type of value returned by the next operation - * @param the type of exception the new resolver may throw + * @param the type of exception the new solver may throw * @param onSuccess a checked function that receives the current value - * and resolves another + * and solves another * @param onError a checked function that receives the error and - * resolves another value - * @return a new handler with either the resolved value or the error + * solves another value + * @return a new handler with either the solved value or the error */ - public ResolveHandler resolve( + public SolveHandler solve( final ThrowingFunction onSuccess, final ThrowingFunction onError ) { return this.value.unwrap( - Maybe.partialResolver(onError), - Maybe.partialResolver(onSuccess) + Maybe.partial(onError), + Maybe.partial(onSuccess) ); } /** - * Chain another resolver function if the value was resolved. Otherwise, + * Chain another solver covering both cases of success or error of the + * previous solver in two different callbacks. + *

+ * The first callback receives the solved value, the second callback the + * caught error. Both should solve another value of the same type {@code S}, + * but only one of the callbacks is invoked. It depends on whether the + * previous value was solved or not. + * + * @param the type of value returned by the next operation + * @param the type of exception the new solver may throw + * @param onSuccess a checked function that receives the current value + * and solves another + * @param onError a checked function that receives the error and + * solves another value + * @return a new handler with either the solved value or the error + * @deprecated in favor of {@link #solve(ThrowingFunction, ThrowingFunction)} + */ + @Deprecated(forRemoval = true, since = "3.4.0") + public SolveHandler resolve(// NOSONAR + final ThrowingFunction onSuccess, + final ThrowingFunction onError + ) { + return this.solve(onSuccess, onError); + } + + /** + * Chain another solver function if the value was solved. Otherwise, * returns a handler containing the error so it can be propagated upstream. * * @param the type of value returned by the next operation - * @param the type of exception the new resolver may throw - * @param resolver a checked function that receives the current value and - * resolves another - * @return a new handler with either the resolved value or an error + * @param the type of exception the new solver may throw + * @param solver a checked function that receives the current value and + * solves another + * @return a new handler with either the solved value or an error */ - public ResolveHandler resolve( - final ThrowingFunction resolver + public SolveHandler solve( + final ThrowingFunction solver ) { return this.value .mapLeft(Commons::cast) .unwrap( - ResolveHandler::ofError, - Maybe.partialResolver(resolver) + SolveHandler::ofError, + Maybe.partial(solver) ); } + /** + * Chain another solver function if the value was solved. Otherwise, + * returns a handler containing the error so it can be propagated upstream. + * + * @param the type of value returned by the next operation + * @param the type of exception the new solver may throw + * @param solver a checked function that receives the current value and + * solves another + * @return a new handler with either the solved value or an error + * @deprecated in favor of {@link #solve(ThrowingFunction)} + */ + @Deprecated(forRemoval = true, since = "3.4.0") + public SolveHandler resolve(// NOSONAR + final ThrowingFunction solver + ) { + return this.solve(solver); + } + /** * Chain the previous operation to an effect covering both the success or * error cases in two different callbacks. @@ -218,18 +262,37 @@ public ResolveHandler resolve( * @return an {@link EffectHandler} representing the result of one of the * invoked callback */ - public EffectHandler runEffect( + public EffectHandler effect( final ThrowingConsumer onSuccess, final ThrowingConsumer onError ) { return this.value.unwrap( - Maybe.partialEffect(onError), - Maybe.partialEffect(onSuccess) + Maybe.partial(onError), + Maybe.partial(onSuccess) ); } /** - * Chain the previous operation to an effect if the value was resolved. + * Chain the previous operation to an effect covering both the success or + * error cases in two different callbacks. + * + * @param the type of the error the effect may throw + * @param onSuccess a consumer checked function to run in case of succeess + * @param onError a consumer checked function to run in case of error + * @return an {@link EffectHandler} representing the result of one of the + * invoked callback + * @deprecated in favor of {@link #effect(ThrowingConsumer, ThrowingConsumer)} + */ + @Deprecated(forRemoval = true, since = "3.4.0") + public EffectHandler runEffect(// NOSONAR + final ThrowingConsumer onSuccess, + final ThrowingConsumer onError + ) { + return this.effect(onSuccess, onError); + } + + /** + * Chain the previous operation to an effect if the value was solved. * Otherwise, returns a handler containing the error so it can be propagated * upstream. * @@ -238,30 +301,48 @@ public EffectHandler runEffect( * @return a new {@link EffectHandler} representing the result of the success * callback or containg the error */ - public EffectHandler runEffect(final ThrowingConsumer effect) { + public EffectHandler effect(final ThrowingConsumer effect) { return this.value .mapLeft(Commons::cast) .unwrap( EffectHandler::ofError, - Maybe.partialEffect(effect) + Maybe.partial(effect) ); } + /** + * Chain the previous operation to an effect if the value was solved. + * Otherwise, returns a handler containing the error so it can be propagated + * upstream. + * + * @param the type of the error the effect may throw + * @param effect a consume checked function to run in case of succeess + * @return a new {@link EffectHandler} representing the result of the success + * callback or containg the error + * @deprecated in favor of {@link #effect(ThrowingConsumer)} + */ + @Deprecated(forRemoval = true, since = "3.4.0") + public EffectHandler runEffect(// NOSONAR + final ThrowingConsumer effect + ) { + return this.effect(effect); + } + /** * If the value is present, map it to another value through the {@code mapper} * function. If the error is present, the {@code mapper} is never applied and * the next handler will still contain the error. * * @param the type the value will be mapped to - * @param mapper a function that receives the resolved value and produces another + * @param mapper a function that receives the solved value and produces another * @return a new handler with either the mapped value, or the previous error */ - public ResolveHandler map(final Function mapper) { + public SolveHandler map(final Function mapper) { return this.value .mapRight(mapper) .unwrap( - ResolveHandler::ofError, - ResolveHandler::ofSuccess + SolveHandler::ofError, + SolveHandler::ofSuccess ); } @@ -275,7 +356,7 @@ public ResolveHandler map(final Function mappe * @return a new handler with either the cast value or a ClassCastException * error */ - public ResolveHandler cast(final Class type) { + public SolveHandler cast(final Class type) { return this.value.unwrap( error -> ofError(new ClassCastException(error.getMessage())), success -> { @@ -289,38 +370,38 @@ public ResolveHandler cast(final Class type) { } /** - * Returns the resolved value if present. Another value otherwise. + * Returns the solved value if present. Another value otherwise. * - * @param fallback the value to return if the operation failed to resolve - * @return the resolved value if present. Another value otherwise + * @param fallback the value to return if the operation failed to solve + * @return the solved value if present. Another value otherwise */ public T orElse(final T fallback) { return this.value.rightOrElse(fallback); } /** - * Returns the resolved value if present. Otherwise, the result produced by + * Returns the solved value if present. Otherwise, the result produced by * the mapping function, which has the error on its argument, and returns * another value. * * @param mapper a function that receives the caught error and produces * another value - * @return the resolved value if present. Another value otherwise + * @return the solved value if present. Another value otherwise */ public T orElse(final Function mapper) { return this.value.unwrap(mapper, Function.identity()); } /** - * Returns the resolved value if present. Otherwise, the result produced by + * Returns the solved value if present. Otherwise, the result produced by * the supplying function as another value. * * @apiNote Use this method instead of {@link #orElse(Object)} to do lazy * evaluation of the produced value. That means that the "else" * value won't be evaluated if the error is not present. * @param supplier the supplying function that produces another value if the - * opration failed to resolve - * @return the resolved value if present. Another value otherwise + * operation failed to solve + * @return the solved value if present. Another value otherwise */ public T orElseGet(final Supplier supplier) { return this.value @@ -329,7 +410,7 @@ public T orElseGet(final Supplier supplier) { } /** - * Returns the resolved value if present. Just {@code null} otherwise. + * Returns the solved value if present. Just {@code null} otherwise. *

* It's strongly encouraged to use {@link #toOptional()} instead to better * handle nullability, but if you really need to return {@code null} in case @@ -338,17 +419,17 @@ public T orElseGet(final Supplier supplier) { * Using {@code .orElse(null)} will result in ambiguity between * {@link #orElse(Function)} and {@link #orElse(Object)}. * - * @return the resolved value if present. Just {@code null} otherwise. + * @return the solved value if present. Just {@code null} otherwise. */ public @Nullable T orNull() { return this.value.rightOrNull(); } /** - * Returns the resolved value if present. Throws the error otherwise. + * Returns the solved value if present. Throws the error otherwise. * - * @return the resolved/handled value if present - * @throws E the error thrown by the {@code resolve} operation + * @return the solved/handled value if present + * @throws E the error thrown by the {@code solve} operation */ public T orThrow() throws E { return this.value @@ -357,12 +438,12 @@ public T orThrow() throws E { } /** - * Returns the value resolved/handled if present. Throws another error otherwise. + * Returns the value solved/handled if present. Throws another error otherwise. * * @param the new error type * @param mapper a function that receives the caught error and produces * another exception - * @return the resolved/handled value if present + * @return the solved/handled value if present * @throws X a mapped exception */ public T orThrow(final Function mapper) throws X { @@ -373,23 +454,23 @@ public T orThrow(final Function ma /** * Transforms the handler to a {@link Maybe} that contains either the - * resolved value or the error. + * solved value or the error. * - * @return the resolved value wrapped in a {@link Maybe} or holding the error + * @return the solved value wrapped in a {@link Maybe} or holding the error */ public Maybe toMaybe() { return this.value .rightToOptional() - .map(Maybe::just) - .orElseGet(Maybe::nothing); + .map(Maybe::of) + .orElseGet(Maybe::empty); } /** - * Transforms the handler to an {@link Optional}. If the value was resolved, + * Transforms the handler to an {@link Optional}. If the value was solved, * the {@link Optional} will contain it. Returs an {@code empty} optional * otherwise. * - * @return the resolved value wrapped in an {@link Optional} if present. An + * @return the solved value wrapped in an {@link Optional} if present. An * {@code empty} optional otherwise. */ public Optional toOptional() { @@ -398,13 +479,13 @@ public Optional toOptional() { /** * Transforms the handler to an {@link Either}, in which the left side might - * contain the error and the right side might contain the resolved value. + * contain the error and the right side might contain the solved value. *

* The benefit of transforming to {@code Either} is that its implementation * ensures that only one of the two possible values is present at the same * time, never both nor none. * - * @return an {@code Either} with the resolved value on the right side or the + * @return an {@code Either} with the solved value on the right side or the * error on the left */ public Either toEither() { @@ -413,55 +494,55 @@ public Either toEither() { /** * Map the value to an {@link AutoCloseable} resource to be use in either a - * {@code resolveClosing} or a {@code runEffectClosing} operation, which will - * close the resource when it completes. If the value was not resolved, the - * error is propagated to the {@link ResourceHolder}. + * {@code solve} or {@code effect} operation. These operations will close the + * resource upon completation. If the value was not solved, the error is + * propagated to the {@link CloseableHandler}. * * @param the type of the {@link AutoCloseable} resource - * @param mapper a function that receives the resolved value and produces an + * @param mapper a function that receives the solved value and produces an * autoclosable resource - * @return a {@link ResourceHolder} with the mapped resource if the value is + * @return a {@link CloseableHandler} with the mapped resource if the value is * present or the error otherwise. * - * @see ResourceHolder#resolveClosing(ThrowingFunction) - * @see ResourceHolder#runEffectClosing(ThrowingConsumer) + * @see CloseableHandler#solve(ThrowingFunction) + * @see CloseableHandler#effect(ThrowingConsumer) */ - public ResourceHolder mapToResource(final Function mapper) { + public CloseableHandler mapToResource(final Function mapper) { return this.value .mapRight(mapper) .unwrap( - ResourceHolder::failure, - ResourceHolder::from + CloseableHandler::failure, + CloseableHandler::from ); } /** - * Resolve a function that may create an {@link AutoCloseable} resource using - * the value in the handle, (if any). If the function is resolved it returns - * a {@link ResourceHolder} that will close the resource after used. If the - * function does not resolves or the value is not present, the error is - * propagated to the {@link ResourceHolder}. + * Solve a function that may create an {@link AutoCloseable} resource using + * the value in the handle, (if any). If the function is solved it returns + * a {@link CloseableHandler} that will close the resource after used. If the + * function does not solves or the value is not present, the error is + * propagated to the {@link CloseableHandler}. * * @param the type of the {@link AutoCloseable} resource * @param the error type the solver function may throw * @param solver a function that returns either a resource or throws an exception - * @return a {@link ResourceHolder} with the solved resource if the value is + * @return a {@link CloseableHandler} with the solved resource if the value is * present or the error otherwise. */ - public ResourceHolder solveResource( + public CloseableHandler solveResource( final ThrowingFunction solver ) { return this.value .mapLeft(Commons::cast) .unwrap( - ResourceHolder::failure, + CloseableHandler::failure, prev -> Maybe - .just(prev) - .resolve(solver) - .map(ResourceHolder::from) - .catchError(ResourceHolder::failure) - .orElse(ResourceHolder::failure) + .of(prev) + .solve(solver) + .map(CloseableHandler::from) + .catchError(CloseableHandler::failure) + .orElse(CloseableHandler::failure) ); } } diff --git a/src/main/java17/io/github/joselion/maybe/Maybe.java b/src/main/java17/io/github/joselion/maybe/Maybe.java index 17e9d87..b3a340d 100644 --- a/src/main/java17/io/github/joselion/maybe/Maybe.java +++ b/src/main/java17/io/github/joselion/maybe/Maybe.java @@ -42,72 +42,147 @@ Optional value() { /** * Creates a {@link Maybe} wrapper of the given value. If the value is - * {@code null}, it returns a {@link #nothing()}. + * {@code null}, it returns a {@link #empty()}. * * @param the type of the value * @param value the value be wrapped * @return a {@code Maybe} wrapping the value if it's non-{@code null}, - * {@link #nothing()} otherwise + * {@link #empty()} otherwise */ - public static Maybe just(final T value) { + public static Maybe of(final @Nullable T value) { return new Maybe<>(value); } /** - * Creates a {@link Maybe} wrapper with nothing on it. This means the wrapper - * does not contains a value because an exception may have occurred. - * + * Creates a {@link Maybe} wrapper of the given optional if not empty. + * Returns {@link #empty()} if the optional is empty or {@code null}. + * + * @apiNote + * It is not convenient to create a {@code Maybe} wrapping an + * {@code Optional}. It'll be hard to use the value later on, and it defies + * the purpose of using {@code Maybe} in the first place (Maybe is like + * Optional, but for handling exceptions). But if you really want to do that + * for some reason, here are some workarounds: + *

+   *  Maybe.of(value).map(Optional::of);
+   *       // ^ can be an `Optional<T>` or not
+   * 
+ * * @param the type of the value - * @return a {@code Maybe} with nothing + * @param value an optional value to create the wrapper from + * @return a {@code Maybe} wrapping the value if it's not empty. + * {@link #empty()} otherwise */ - public static Maybe nothing() { + public static Maybe of(final @Nullable Optional value) { // NOSONAR + if (value != null) { // NOSONAR + return value + .map(Maybe::new) + .orElseGet(Maybe::empty); + } + return new Maybe<>(null); } + /** + * Creates a {@link Maybe} wrapper of the given value. If the value is + * {@code null}, it returns a {@link #empty()}. + * + * @param the type of the value + * @param value the value be wrapped + * @return a {@code Maybe} wrapping the value if it's non-{@code null}, + * {@link #empty()} otherwise + * @deprecated in favor of {@link #of(Object)} + */ + @Deprecated(forRemoval = true, since = "3.4.0") + public static Maybe just(final @Nullable T value) { // NOSONAR + return Maybe.of(value); + } + + /** + * Creates an empty {@link Maybe} instance. + * + * @param the type of the value + * @return an empty {@code Maybe} + */ + public static Maybe empty() { + return Maybe.of(null); + } + + /** + * Creates an empty {@link Maybe} instance. + * + * @param the type of the value + * @return an empty {@code Maybe} + * @deprecated in favor of {@link #empty()} + */ + @Deprecated(forRemoval = true, since = "3.4.0") + public static Maybe nothing() { // NOSONAR + return Maybe.empty(); + } + /** * Creates a {@link Maybe} wrapper of the given value if the optional is not - * empty. Returns a {@link #nothing()} otherwise. + * empty. Returns a {@link #empty()} otherwise. *

* This is a convenience creator that would be equivalent to: *

-   *  Maybe.just(opt)
-   *    .resolve(Optional::get)
+   *  Maybe.of(opt)
+   *    .solve(Optional::get)
    *    .toMaybe();
    * 
* * @param the type of the value * @param value an optional value to create the wrapper from * @return a {@code Maybe} wrapping the value if it's not empty. - * {@link #nothing()} otherwise + * {@link #empty()} otherwise + * @deprecated in favor of {@link #of(Optional)} */ - public static Maybe fromOptional(final Optional value) { - return value - .map(Maybe::new) - .orElseGet(Maybe::nothing); + @Deprecated(forRemoval = true, since = "3.4.0") + public static Maybe fromOptional(final Optional value) { // NOSONAR + return Maybe.of(value); } /** - * Resolves the value of a throwing operation using a {@link ThrowingSupplier} - * expression. Returning then a {@link ResolveHandler} which allows to handle + * Solves the value of a throwing operation using a {@link ThrowingSupplier} + * expression. Returning then a {@link SolveHandler} which allows to handle * the possible error and return a safe value. * - * @param the type of the value returned by the {@code resolver} - * @param the type of exception the {@code resolver} may throw - * @param resolver the checked supplier operation to resolve - * @return a {@link ResolveHandler} with either the value resolved or the thrown + * @param the type of the value returned by the {@code solver} + * @param the type of exception the {@code solver} may throw + * @param solver the checked supplier operation to solve + * @return a {@link SolveHandler} with either the value solved or the thrown * exception to be handled */ - public static ResolveHandler fromResolver( - final ThrowingSupplier resolver + public static SolveHandler from( + final ThrowingSupplier solver ) { try { - return ResolveHandler.ofSuccess(resolver.get()); + return SolveHandler.ofSuccess(solver.get()); } catch (Throwable e) { // NOSONAR final var error = Commons.cast(e); - return ResolveHandler.ofError(error); + return SolveHandler.ofError(error); } } + /** + * Solves the value of a throwing operation using a {@link ThrowingSupplier} + * expression. Returning then a {@link SolveHandler} which allows to handle + * the possible error and return a safe value. + * + * @param the type of the value returned by the {@code solver} + * @param the type of exception the {@code solver} may throw + * @param solver the checked supplier operation to solve + * @return a {@link SolveHandler} with either the value solved or the thrown + * exception to be handled + * @deprecated in favor of {@link #from(ThrowingSupplier)} + */ + @Deprecated(forRemoval = true, since = "3.4.0") + public static SolveHandler fromResolver(// NOSONAR + final ThrowingSupplier solver + ) { + return Maybe.from(solver); + } + /** * Runs an effect that may throw an exception using a {@link ThrowingRunnable} * expression. Returning then an {@link EffectHandler} which allows to handle @@ -116,9 +191,9 @@ public static ResolveHandler fromResolver( * @param the type of exception the {@code effect} may throw * @param effect the checked runnable operation to execute * @return an {@link EffectHandler} with either the thrown exception to be - * handled or nothing + * handled or empty */ - public static EffectHandler fromEffect(final ThrowingRunnable effect) { + public static EffectHandler from(final ThrowingRunnable effect) { try { effect.run(); return EffectHandler.empty(); @@ -129,16 +204,66 @@ public static EffectHandler fromEffect(final ThrowingRu } /** - * Convenience partial application of a {@code resolver}. This method creates + * Runs an effect that may throw an exception using a {@link ThrowingRunnable} + * expression. Returning then an {@link EffectHandler} which allows to handle + * the possible error. + * + * @param the type of exception the {@code effect} may throw + * @param effect the checked runnable operation to execute + * @return an {@link EffectHandler} with either the thrown exception to be + * handled or empty + * @deprecated in favor of {@link #from(ThrowingRunnable)} + */ + @Deprecated(forRemoval = true, since = "3.4.0") + public static EffectHandler fromEffect(// NOSONAR + final ThrowingRunnable effect + ) { + return Maybe.from(effect); + } + + /** + * Convenience partial application of a {@code solver}. This method creates * a function that receives an {@code S} value which can be used to produce a - * {@link ResolveHandler} once applied. This is specially useful when we want + * {@link SolveHandler} once applied. This is specially useful when we want * to create a {@link Maybe} from a callback argument, like on a * {@link Optional#map(Function)} for instance. *

* In other words, the following code *

    *  Optional.of(value)
-   *    .map(str -> Maybe.fromResolver(() -> decode(str)));
+   *    .map(str -> Maybe.from(() -> decode(str)));
+   * 
+ * Is equivalent to + *
+   *  Optional.of(value)
+   *    .map(Maybe.partial(this::decode));
+   * 
+ * + * @param the type of the value the returned function receives + * @param the type of the value to be solved + * @param the type of the error the solver may throw + * @param solver a checked function that receives an {@code S} value and + * returns a {@code T} value + * @return a partially applied {@link SolveHandler}. This means, a function + * that receives an {@code S} value, and produces a {@code SolveHandler} + */ + public static Function> partial( + final ThrowingFunction solver + ) { + return value -> Maybe.from(() -> solver.apply(value)); + } + + /** + * Convenience partial application of a {@code solver}. This method creates + * a function that receives an {@code S} value which can be used to produce a + * {@link SolveHandler} once applied. This is specially useful when we want + * to create a {@link Maybe} from a callback argument, like on a + * {@link Optional#map(Function)} for instance. + *

+ * In other words, the following code + *

+   *  Optional.of(value)
+   *    .map(str -> Maybe.from(() -> decode(str)));
    * 
* Is equivalent to *
@@ -147,17 +272,19 @@ public static  EffectHandler fromEffect(final ThrowingRu
    * 
* * @param the type of the value the returned function receives - * @param the type of the value to be resolved - * @param the type of the error the resolver may throw - * @param resolver a checked function that receives an {@code S} value and + * @param the type of the value to be solved + * @param the type of the error the solver may throw + * @param solver a checked function that receives an {@code S} value and * returns a {@code T} value - * @return a partially applied {@link ResolveHandler}. This means, a function - * that receives an {@code S} value, and produces a {@code ResolveHandler} + * @return a partially applied {@link SolveHandler}. This means, a function + * that receives an {@code S} value, and produces a {@code SolveHandler} + * @deprecated in favor of {@link #partial(ThrowingFunction)} */ - public static Function> partialResolver( - final ThrowingFunction resolver + @Deprecated(forRemoval = true, since = "3.4.0") + public static Function> partialResolver(// NOSONAR + final ThrowingFunction solver ) { - return value -> Maybe.fromResolver(() -> resolver.apply(value)); + return Maybe.partial(solver); } /** @@ -170,7 +297,37 @@ public static Function> part * In other words, the following code *
    *  Optional.of(value)
-   *    .map(msg -> Maybe.fromEffect(() -> sendMessage(msg)));
+   *    .map(msg -> Maybe.from(() -> sendMessage(msg)));
+   * 
+ * Is equivalent to + *
+   *  Optional.of(value)
+   *    .map(Maybe.partial(this::sendMessage));
+   * 
+ * + * @param the type of the value the returned function receives + * @param the type of the error the effect may throw + * @param effect a checked consumer that receives an {@code S} value + * @return a partially applied {@link EffectHandler}. This means, a function + * that receives an {@code S} value, and produces an {@code EffectHandler} + */ + public static Function> partial( + final ThrowingConsumer effect + ) { + return value -> Maybe.from(() -> effect.accept(value)); + } + + /** + * Convenience partial application of an {@code effect}. This method creates + * a function that receives an {@code S} value which can be used to produce + * an {@link EffectHandler} once applied. This is specially useful when we + * want to create a {@link Maybe} from a callback argument, like on a + * {@link Optional#map(Function)} for instance. + *

+ * In other words, the following code + *

+   *  Optional.of(value)
+   *    .map(msg -> Maybe.from(() -> sendMessage(msg)));
    * 
* Is equivalent to *
@@ -179,73 +336,75 @@ public static  Function> part
    * 
* * @param the type of the value the returned function receives - * @param the type of the error the resolver may throw + * @param the type of the error the effect may throw * @param effect a checked consumer that receives an {@code S} value * @return a partially applied {@link EffectHandler}. This means, a function * that receives an {@code S} value, and produces an {@code EffectHandler} + * @deprecated in favor of {@link #partial(ThrowingConsumer)} */ - public static Function> partialEffect( + @Deprecated(forRemoval = true, since = "3.4.0") + public static Function> partialEffect(// NOSONAR final ThrowingConsumer effect ) { - return value -> Maybe.fromEffect(() -> effect.accept(value)); + return Maybe.partial(effect); } /** - * Prepare an {@link AutoCloseable} resource to use in a resolver or effect. + * Prepare an {@link AutoCloseable} resource to use in a solver or effect. * The resource will be automatically closed after the operation is finished, * just like a common try-with-resources statement. * * @param the type of the resource. Extends from {@link AutoCloseable} * @param the type of error the holder may have * @param resource the {@link AutoCloseable} resource to prepare - * @return a {@link ResourceHolder} which let's you choose to resolve a value + * @return a {@link CloseableHandler} which let's you choose to solve a value * or run an effect using the prepared resource */ - public static ResourceHolder withResource(final R resource) { - return ResourceHolder.from(resource); + public static CloseableHandler withResource(final R resource) { + return CloseableHandler.from(resource); } /** - * Prepare an {@link AutoCloseable} resource to use in a resolver or effect, + * Prepare an {@link AutoCloseable} resource to use in a solver or effect, * using a {@link ThrowingSupplier}. Any exception thrown by the supplier - * will be propageted to the {@link ResourceHolder}. The resource will be + * will be propageted to the {@link CloseableHandler}. The resource will be * automatically closed after the operation is finished, just like a common * try-with-resources statement. * * @param the type of the resource. Extends from {@link AutoCloseable} * @param the type of error the holder may have * @param supplier the throwing supplier o the {@link AutoCloseable} resource - * @return a {@link ResourceHolder} which let's you choose to resolve a value + * @return a {@link CloseableHandler} which let's you choose to solve a value * or run an effect using the prepared resource */ - public static ResourceHolder solveResource( + public static CloseableHandler solveResource( final ThrowingSupplier supplier ) { return Maybe - .fromResolver(supplier) - .map(ResourceHolder::from) - .orElse(ResourceHolder::failure); + .from(supplier) + .map(CloseableHandler::from) + .orElse(CloseableHandler::failure); } /** * If present, maps the value to another using the provided mapper function. - * Otherwise, ignores the mapper and returns {@link #nothing()}. + * Otherwise, ignores the mapper and returns {@link #empty()}. * * @param the type the value will be mapped to * @param mapper the mapper function * @return a {@code Maybe} with the mapped value if present, - * {@link #nothing()} otherwise + * {@link #empty()} otherwise */ public Maybe map(final Function mapper) { return Maybe - .fromOptional(this.value) - .resolve(mapper::apply) + .of(this.value) + .solve(mapper::apply) .toMaybe(); } /** * If present, maps the value to another using the provided mapper function. - * Otherwise, ignores the mapper and returns {@link #nothing()}. + * Otherwise, ignores the mapper and returns {@link #empty()}. * * This method is similar to {@link #map(Function)}, but the mapping function is * one whose result is already a {@code Maybe}, and if invoked, flatMap does not @@ -254,41 +413,61 @@ public Maybe map(final Function mapper) { * @param the type the value will be mapped to * @param mapper the mapper function * @return a {@code Maybe} with the mapped value if present, - * {@link #nothing()} otherwise + * {@link #empty()} otherwise */ public Maybe flatMap(final Function> mapper) { return Maybe - .fromOptional(this.value) - .resolve(mapper::apply) + .of(this.value) + .solve(mapper::apply) .map(Commons::>cast) - .orElseGet(Maybe::nothing); + .orElseGet(Maybe::empty); } /** - * Chain the {@code Maybe} with another resolver, if and only if the previous + * Chain the {@code Maybe} with another solver, if and only if the previous * operation was handled with no errors. The value of the previous operation * is passed as argument of the {@link ThrowingFunction}. * * @param the type of value returned by the next operation - * @param the type of exception the new resolver may throw - * @param resolver a checked function that receives the current value and - * resolves another - * @return a {@link ResolveHandler} with either the resolved value, or the + * @param the type of exception the new solver may throw + * @param solver a checked function that receives the current value and + * solves another + * @return a {@link SolveHandler} with either the solved value, or the * thrown exception to be handled */ - public ResolveHandler resolve( - final ThrowingFunction resolver + public SolveHandler solve( + final ThrowingFunction solver ) { try { return this.value - .map(Maybe.partialResolver(resolver)) + .map(Maybe.partial(solver)) .orElseThrow(); } catch (final NoSuchElementException e) { final var error = Commons.cast(e); - return ResolveHandler.ofError(error); + return SolveHandler.ofError(error); } } + /** + * Chain the {@code Maybe} with another solver, if and only if the previous + * operation was handled with no errors. The value of the previous operation + * is passed as argument of the {@link ThrowingFunction}. + * + * @param the type of value returned by the next operation + * @param the type of exception the new solver may throw + * @param solver a checked function that receives the current value and + * solves another + * @return a {@link SolveHandler} with either the solved value, or the + * thrown exception to be handled + * @deprecated in favor of {@link #solve(ThrowingFunction)} + */ + @Deprecated(forRemoval = true, since = "3.4.0") + public SolveHandler resolve(// NOSONAR + final ThrowingFunction solver + ) { + return this.solve(solver); + } + /** * Chain the {@code Maybe} with another effect, if and only if the previous * operation was handled with no errors. @@ -298,10 +477,10 @@ public ResolveHandler resolve( * @return an {@link EffectHandler} with either the thrown exception to be * handled or nothing */ - public EffectHandler runEffect(final ThrowingConsumer effect) { + public EffectHandler effect(final ThrowingConsumer effect) { try { return this.value - .map(Maybe.partialEffect(effect)) + .map(Maybe.partial(effect)) .orElseThrow(); } catch (final NoSuchElementException e) { final var error = Commons.cast(e); @@ -309,19 +488,36 @@ public EffectHandler runEffect(final ThrowingConsumer the type of exception the new effect may throw + * @param effect the checked runnable operation to execute next + * @return an {@link EffectHandler} with either the thrown exception to be + * handled or nothing + * @deprecated in favor of {@link #effect(ThrowingConsumer)} + */ + @Deprecated(forRemoval = true, since = "3.4.0") + public EffectHandler runEffect(// NOSONAR + final ThrowingConsumer effect + ) { + return this.effect(effect); + } + /** * If the value is present, cast the value to another type. In case of an - * exception during the cast, a Maybe with {@link #nothing()} is returned. + * exception during the cast, a Maybe with {@link #empty()} is returned. * * @param the type that the value will be cast to * @param type the class instance of the type to cast * @return a new {@code Maybe} with the cast value if it can be cast, - * {@link #nothing()} otherwise + * {@link #empty()} otherwise */ public Maybe cast(final Class type) { return Maybe - .fromOptional(this.value) - .resolve(type::cast) + .of(this.value) + .solve(type::cast) .toMaybe(); } @@ -335,14 +531,25 @@ public boolean hasValue() { } /** - * Checks if the {@code Maybe} has nothing. That is, when no value is present. + * Checks if the {@code Maybe} is empty. That is, when no value is present. * - * @return true if the value is NOT present, false otherwise + * @return true if the value is not present, false otherwise */ - public boolean hasNothing() { + public boolean isEmpty() { return this.value.isEmpty(); } + /** + * Checks if the {@code Maybe} is empty. That is, when no value is present. + * + * @return true if the value is not present, false otherwise + * @deprecated in favor of {@link #isEmpty()} + */ + @Deprecated(forRemoval = true, since = "3.4.0") + public boolean hasNothing() { // NOSONAR + return this.isEmpty(); + } + /** * Safely unbox the value as an {@link Optional} which may or may not contain * a value. @@ -401,6 +608,6 @@ public String toString() { return this.value .map(Object::toString) .map("Maybe[%s]"::formatted) - .orElse("Maybe.nothing"); + .orElse("Maybe.empty"); } } diff --git a/src/test/java/io/github/joselion/maybe/ResourceHolderTests.java b/src/test/java/io/github/joselion/maybe/CloseableHandlerTests.java similarity index 74% rename from src/test/java/io/github/joselion/maybe/ResourceHolderTests.java rename to src/test/java/io/github/joselion/maybe/CloseableHandlerTests.java index 7b586fd..02136bd 100644 --- a/src/test/java/io/github/joselion/maybe/ResourceHolderTests.java +++ b/src/test/java/io/github/joselion/maybe/CloseableHandlerTests.java @@ -5,6 +5,7 @@ import static org.assertj.core.api.InstanceOfAssertFactories.THROWABLE; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import java.io.FileInputStream; @@ -18,15 +19,15 @@ import io.github.joselion.testing.Spy; import io.github.joselion.testing.UnitTest; -@UnitTest class ResourceHolderTests { +@UnitTest class CloseableHandlerTests { private static final String FILE_PATH = "./src/test/resources/readTest.txt"; private static final IOException FAIL_EXCEPTION = new IOException("FAIL"); - private final ThrowingFunction okResolver = res -> "OK"; + private final ThrowingFunction okSolver = res -> "OK"; - private final ThrowingFunction errorResolver = res -> { + private final ThrowingFunction errorSolver = res -> { throw FAIL_EXCEPTION; }; @@ -36,14 +37,14 @@ throw FAIL_EXCEPTION; }; - @Nested class resolveClosing { + @Nested class solve { @Nested class when_the_resource_is_present { @Nested class when_the_operation_succeeds { @Test void returns_a_handler_with_the_value() { final var fis = getFIS(); - final var resolverSpy = Spy.lambda(okResolver); + final var solverSpy = Spy.lambda(okSolver); final var handler = Maybe.withResource(fis) - .resolveClosing(resolverSpy); + .solve(solverSpy); assertThat(handler.success()).contains("OK"); assertThat(handler.error()).isEmpty(); @@ -51,16 +52,16 @@ .isExactlyInstanceOf(IOException.class) .hasMessage("Stream Closed"); - verify(resolverSpy).apply(fis); + verify(solverSpy).apply(fis); } } @Nested class when_the_operation_fails { @Test void returns_a_handler_with_the_error() throws IOException { final var fis = getFIS(); - final var resolverSpy = Spy.lambda(errorResolver); + final var solverSpy = Spy.lambda(errorSolver); final var handler = Maybe.withResource(fis) - .resolveClosing(resolverSpy); + .solve(solverSpy); assertThat(handler.success()).isEmpty(); assertThat(handler.error()).contains(FAIL_EXCEPTION); @@ -68,7 +69,7 @@ .isExactlyInstanceOf(IOException.class) .hasMessage("Stream Closed"); - verify(resolverSpy).apply(fis); + verify(solverSpy).apply(fis); } } } @@ -76,9 +77,9 @@ @Nested class when_the_error_is_present { @Test void returns_a_handler_with_the_propagated_error() throws Throwable { final var error = new IOException("Something went wrong..."); - final var resolverSpy = Spy.>lambda(fis -> ""); - final var handler = ResourceHolder.failure(error) - .resolveClosing(resolverSpy); + final var solverSpy = Spy.>lambda(fis -> ""); + final var handler = CloseableHandler.failure(error) + .solve(solverSpy); assertThat(handler.success()).isEmpty(); assertThat(handler.error()) @@ -86,19 +87,30 @@ .isExactlyInstanceOf(IOException.class) .hasMessage(error.getMessage()); - verify(resolverSpy, never()).apply(any()); + verify(solverSpy, never()).apply(any()); } } } - @Nested class runEffectClosing { + @Nested class resolveClosing { + @Test void calls_solve() { + final var identity = ThrowingFunction.identity(); + final var error = new IOException("Something went wrong..."); + final var handler = spy(CloseableHandler.failure(error)); + handler.resolveClosing(identity); + + verify(handler).solve(identity); + } + } + + @Nested class effect { @Nested class when_the_resource_is_present { @Nested class when_the_operation_succeeds { - @Test void returns_a_handler_with_nothing() { + @Test void returns_an_empty_handler() { final var fis = getFIS(); final var effectSpy = Spy.lambda(noOpEffect); final var handler = Maybe.withResource(fis) - .runEffectClosing(effectSpy); + .effect(effectSpy); assertThat(handler.error()).isEmpty(); assertThatThrownBy(fis::read) @@ -114,7 +126,7 @@ final var fis = getFIS(); final var effectSpy = Spy.lambda(errorEffect); final var handler = Maybe.withResource(fis) - .runEffectClosing(effectSpy); + .effect(effectSpy); assertThat(handler.error()).contains(FAIL_EXCEPTION); assertThatThrownBy(fis::read) @@ -130,8 +142,8 @@ @Test void returns_a_handler_with_the_propagated_error() throws Throwable { final var error = new IOException("Something went wrong..."); final var effectSpy = Spy.>lambda(res -> { }); - final var handler = ResourceHolder.failure(error) - .runEffectClosing(effectSpy); + final var handler = CloseableHandler.failure(error) + .effect(effectSpy); assertThat(handler.error()) .get(THROWABLE) @@ -143,9 +155,18 @@ } } + @Nested class runEffectClosing { + @Test void calls_effect() { + final var handler = spy(CloseableHandler.failure(FAIL_EXCEPTION)); + handler.runEffectClosing(noOpEffect); + + verify(handler).effect(noOpEffect); + } + } + private FileInputStream getFIS() { - return Maybe.just(FILE_PATH) - .resolve(FileInputStream::new) + return Maybe.of(FILE_PATH) + .solve(FileInputStream::new) .orThrow(Error::new); } } diff --git a/src/test/java/io/github/joselion/maybe/EffectHandlerTests.java b/src/test/java/io/github/joselion/maybe/EffectHandlerTests.java index 47493cc..ecc87c3 100644 --- a/src/test/java/io/github/joselion/maybe/EffectHandlerTests.java +++ b/src/test/java/io/github/joselion/maybe/EffectHandlerTests.java @@ -5,6 +5,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -35,7 +36,7 @@ @Test void calls_the_effect_callback() { final var runnableSpy = Spy.runnable(() -> { }); - Maybe.fromEffect(noOp).doOnSuccess(runnableSpy); + Maybe.from(noOp).doOnSuccess(runnableSpy); verify(runnableSpy, times(1)).run(); } @@ -45,7 +46,7 @@ @Test void never_calls_the_effect_callback() { final var runnableSpy = Spy.runnable(() -> { }); - Maybe.fromEffect(throwingOp).doOnSuccess(runnableSpy); + Maybe.from(throwingOp).doOnSuccess(runnableSpy); verify(runnableSpy, never()).run(); } @@ -59,7 +60,7 @@ @Test void calls_the_effect_callback() { final var consumerSpy = Spy.consumer(error -> { }); - Maybe.fromEffect(throwingOp) + Maybe.from(throwingOp) .doOnError(FileSystemException.class, consumerSpy); verify(consumerSpy, times(1)).accept(FAIL_EXCEPTION); @@ -70,7 +71,7 @@ @Test void never_calls_the_effect_callback() { final var consumerSpy = Spy.consumer(error -> { }); - Maybe.fromEffect(throwingOp) + Maybe.from(throwingOp) .doOnError(RuntimeException.class, consumerSpy); verify(consumerSpy, never()).accept(any()); @@ -82,7 +83,7 @@ @Test void calls_the_effect_callback() { final var consumerSpy = Spy.consumer(error -> { }); - Maybe.fromEffect(throwingOp) + Maybe.from(throwingOp) .doOnError(consumerSpy); verify(consumerSpy, times(1)).accept(FAIL_EXCEPTION); @@ -94,7 +95,7 @@ @Test void never_calls_the_effect_callback() { final var cunsumerSpy = Spy.consumer(error -> { }); - Maybe.fromEffect(noOp) + Maybe.from(noOp) .doOnError(RuntimeException.class, cunsumerSpy) .doOnError(cunsumerSpy); @@ -109,7 +110,7 @@ @Nested class and_the_error_is_an_instance_of_the_provided_type { @Test void calls_the_handler_function() { final var consumerSpy = Spy.consumer(e -> { }); - final var handler = Maybe.fromEffect(throwingOp) + final var handler = Maybe.from(throwingOp) .catchError(FileSystemException.class, consumerSpy); assertThat(handler.error()).isEmpty(); @@ -121,7 +122,7 @@ @Nested class and_the_error_is_not_an_instance_of_the_provided_type { @Test void never_calls_the_handler_function() { final var consumerSpy = Spy.consumer(e -> { }); - final var handler = Maybe.fromEffect(throwingOp) + final var handler = Maybe.from(throwingOp) .catchError(AccessDeniedException.class, consumerSpy); assertThat(handler.error()).contains(FAIL_EXCEPTION); @@ -134,7 +135,7 @@ @Nested class and_the_error_type_is_not_provided { @Test void calls_the_handler_function() { final var consumerSpy = Spy.consumer(e -> { }); - final var handler = Maybe.fromEffect(throwingOp) + final var handler = Maybe.from(throwingOp) .catchError(consumerSpy); assertThat(handler.error()).isEmpty(); @@ -148,8 +149,8 @@ @Test void never_calls_the_handler_function() { final var consumerSpy = Spy.consumer(e -> { }); final var handlers = List.of( - Maybe.fromEffect(noOp).catchError(RuntimeException.class, consumerSpy), - Maybe.fromEffect(noOp).catchError(consumerSpy) + Maybe.from(noOp).catchError(RuntimeException.class, consumerSpy), + Maybe.from(noOp).catchError(consumerSpy) ); assertThat(handlers).isNotEmpty().allSatisfy(handler -> { @@ -161,7 +162,7 @@ } } - @Nested class runEffect { + @Nested class effect { @Nested class when_the_error_is_not_present { @Test void calls_the_effect_callback_and_returns_a_new_handler() throws FileSystemException { final var effectSpy = Spy.lambda(throwingOp); @@ -169,10 +170,10 @@ final var errorSpy = Spy.>lambda( err -> throwingOp.run() ); - final var handler = Maybe.fromEffect(noOp); + final var handler = Maybe.from(noOp); final var newHandlers = List.of( - handler.runEffect(effectSpy), - handler.runEffect(successSpy, errorSpy) + handler.effect(effectSpy), + handler.effect(successSpy, errorSpy) ); assertThat(newHandlers).isNotEmpty().allSatisfy(newHandler -> { @@ -193,8 +194,8 @@ final var errorSpy = Spy.>lambda( err -> throwingOp.run() ); - final var handler = Maybe.fromEffect(throwingOp); - final var newHandler = handler.runEffect(successSpy, errorSpy); + final var handler = Maybe.from(throwingOp); + final var newHandler = handler.effect(successSpy, errorSpy); assertThat(newHandler).isNotSameAs(handler); assertThat(newHandler.error()).contains(FAIL_EXCEPTION); @@ -207,8 +208,8 @@ @Nested class and_the_error_callback_is_not_provided { @Test void never_calls_the_effect_callback_and_returns_a_new_empty_handler() throws FileSystemException { final var effectSpy = Spy.lambda(throwingOp); - final var handler = Maybe.fromEffect(throwingOp); - final var newHandler = handler.runEffect(effectSpy); + final var handler = Maybe.from(throwingOp); + final var newHandler = handler.effect(effectSpy); assertThat(newHandler).isNotSameAs(handler); assertThat(newHandler.error()).isEmpty(); @@ -219,11 +220,24 @@ } } + @Nested class runEffect { + @Test void calls_effect() { + final var onSuccess = Spy.>lambda(() -> { }); + final var onError = Spy.>lambda(error -> { }); + final var handler = spy(Maybe.from(throwingOp)); + handler.runEffect(onSuccess, onError); + handler.runEffect(onSuccess); + + verify(handler).effect(onSuccess, onError); + verify(handler).effect(onSuccess); + } + } + @Nested class orElse { @Nested class when_the_error_is_present { @Test void calls_the_effect_callback() { final var consumerSpy = Spy.consumer(e -> { }); - final var handler = Maybe.fromEffect(throwingOp); + final var handler = Maybe.from(throwingOp); handler.orElse(consumerSpy); @@ -234,7 +248,7 @@ @Nested class when_the_error_is_not_present { @Test void never_calls_the_effect_callback() { final var consumerSpy = Spy.consumer(e -> { }); - final var handler = Maybe.fromEffect(noOp); + final var handler = Maybe.from(noOp); handler.orElse(consumerSpy); @@ -248,7 +262,7 @@ @Test void throws_the_error() { final var anotherError = new RuntimeException("OTHER"); final var functionSpy = Spy.function((FileSystemException err) -> anotherError); - final var handler = Maybe.fromEffect(throwingOp); + final var handler = Maybe.from(throwingOp); assertThatThrownBy(handler::orThrow).isEqualTo(FAIL_EXCEPTION); assertThatThrownBy(() -> handler.orThrow(functionSpy)).isEqualTo(anotherError); @@ -260,7 +274,7 @@ @Nested class when_the_error_is_not_present { @Test void no_exception_is_thrown() { final var functionSpy = Spy.function((RuntimeException err) -> FAIL_EXCEPTION); - final var handler = Maybe.fromEffect(noOp); + final var handler = Maybe.from(noOp); assertThatCode(handler::orThrow).doesNotThrowAnyException(); assertThatCode(() -> handler.orThrow(functionSpy)).doesNotThrowAnyException(); diff --git a/src/test/java/io/github/joselion/maybe/MaybeTests.java b/src/test/java/io/github/joselion/maybe/MaybeTests.java index a620532..c14eeb7 100644 --- a/src/test/java/io/github/joselion/maybe/MaybeTests.java +++ b/src/test/java/io/github/joselion/maybe/MaybeTests.java @@ -5,7 +5,10 @@ import static org.assertj.core.api.InstanceOfAssertFactories.THROWABLE; import static org.assertj.core.api.InstanceOfAssertFactories.optional; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.CALLS_REAL_METHODS; +import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -48,141 +51,219 @@ throw FAIL_EXCEPTION; }; - @Nested class just { - @Nested class when_a_value_is_passed { - @Test void returns_a_Maybe_wrapping_the_value() { - final var maybe = Maybe.just(OK); + @Nested class of { + @Nested class when_the_value_is_not_an_Optional { + @Nested class when_a_value_is_passed { + @Test void returns_a_Maybe_wrapping_the_value() { + final var maybe = Maybe.of(OK); + + assertThat(maybe.value()).contains(OK); + } + } + + @Nested class when_null_is_passed { + @Test void returns_an_empty_Maybe() { + final var maybe = Maybe.of(null); - assertThat(maybe.value()).contains(OK); + assertThat(maybe.value()).isEmpty(); + } } } - @Nested class when_null_is_passed { - @Test void returns_a_Maybe_wrapping_nothing() { - final var maybe = Maybe.just(null); + @Nested class when_the_value_is_an_Optional { + @Nested class when_the_optional_has_a_value { + @Test void returns_a_Maybe_wrapping_the_value() { + final var maybe = Maybe.of(Optional.of(OK)); - assertThat(maybe.value()).isEmpty(); + assertThat(maybe.value()).contains(OK); + } + } + + @Nested class when_the_optional_is_empty { + @Test void returns_an_empty_Maybe() { + final var maybe = Maybe.of(Optional.empty()); + + assertThat(maybe.value()).isEmpty(); + } } } } - @Nested class nothing { - @Test void returns_a_Maybe_wrapping_nothing() { - final var maybe = Maybe.nothing(); + @Nested class just { + @Nested class calls_of { + @Test void returns_a_Maybe_wrapping_the_value() { + try (var maybe = mockStatic(Maybe.class, CALLS_REAL_METHODS)) { + Maybe.just(OK); + + maybe.verify(() -> Maybe.of(OK)); + } + } + } + } + + @Nested class empty { + @Test void returns_an_empty_Maybe() { + final var maybe = Maybe.empty(); assertThat(maybe.value()).isEmpty(); } } - @Nested class fromOptional { - @Nested class when_the_optional_has_a_value { - @Test void returns_a_Maybe_wrapping_the_value() { - final var maybe = Maybe.fromOptional(Optional.of(OK)); + @Nested class nothing { + @Test void calls_empty() { + try (var maybe = mockStatic(Maybe.class, CALLS_REAL_METHODS)) { + Maybe.nothing(); - assertThat(maybe.value()).contains(OK); + maybe.verify(() -> Maybe.empty()); } } + } - @Nested class when_the_optional_is_empty { - @Test void returns_a_Maybe_wrapping_nothing() { - final var maybe = Maybe.fromOptional(Optional.empty()); + @Nested class fromOptional { + @Test void calls_of() { + try (var maybe = mockStatic(Maybe.class, CALLS_REAL_METHODS)) { + Maybe.fromOptional(Optional.empty()); - assertThat(maybe.value()).isEmpty(); + maybe.verify(() -> Maybe.of(Optional.empty())); } } } - @Nested class fromResolver { - @Nested class when_the_operation_succeeds { - @Test void returns_a_handler_with_the_value() throws IOException { - final var supplierSpy = Spy.>lambda(() -> OK); - final var handler = Maybe.fromResolver(supplierSpy); + @Nested class from { + @Nested class when_a_value_is_provided { + @Nested class and_the_operation_succeeds { + @Test void returns_a_handler_with_the_value() throws IOException { + final var supplierSpy = Spy.>lambda(() -> OK); + final var handler = Maybe.from(supplierSpy); - assertThat(handler.success()).contains(OK); - assertThat(handler.error()).isEmpty(); + assertThat(handler.success()).contains(OK); + assertThat(handler.error()).isEmpty(); - verify(supplierSpy, times(1)).get(); + verify(supplierSpy, times(1)).get(); + } + } + + @Nested class and_the_operation_fails { + @Test void returns_a_handler_with_the_error() throws IOException { + final var supplierSpy = Spy.lambda(failSupplier); + final var handler = Maybe.from(supplierSpy); + + assertThat(handler.success()).isEmpty(); + assertThat(handler.error()).contains(FAIL_EXCEPTION); + + verify(supplierSpy, times(1)).get(); + } } } - @Nested class when_the_operation_fails { - @Test void returns_a_handler_with_the_error() throws IOException { - final var supplierSpy = Spy.lambda(failSupplier); - final var handler = Maybe.fromResolver(supplierSpy); + @Nested class when_an_effect_is_passed { + @Nested class and_the_operation_succeeds { + @Test void returns_an_empty_handler() { + final var runnableSpy = Spy.>lambda(() -> { }); + final var handler = Maybe.from(runnableSpy); - assertThat(handler.success()).isEmpty(); - assertThat(handler.error()).contains(FAIL_EXCEPTION); + assertThat(handler.error()).isEmpty(); + + verify(runnableSpy, times(1)).run(); + } + } + + @Nested class and_the_operation_fails { + @Test void returns_a_handler_with_the_error() throws IOException { + final var runnableSpy = Spy.lambda(failRunnable); + final var handler = Maybe.from(runnableSpy); + + assertThat(handler.error()).contains(FAIL_EXCEPTION); + + verify(runnableSpy, times(1)).run(); + } + } + } + } + + @Nested class fromResolver { + @Test void calls_from() { + try (var maybe = mockStatic(Maybe.class, CALLS_REAL_METHODS)) { + Maybe.fromResolver(failSupplier); - verify(supplierSpy, times(1)).get(); + maybe.verify(() -> Maybe.from(failSupplier)); } } } @Nested class fromEffect { - @Nested class when_the_operation_succeeds { - @Test void returns_a_handler_with_nothing() { - final var runnableSpy = Spy.>lambda(() -> { }); - final var handler = Maybe.fromEffect(runnableSpy); + @Test void class_from() { + try (var maybe = mockStatic(Maybe.class, CALLS_REAL_METHODS)) { + Maybe.fromEffect(failRunnable); - assertThat(handler.error()).isEmpty(); + maybe.verify(() -> Maybe.from(failRunnable)); + } + } + } + + @Nested class partial { + @Nested class when_a_function_is_provided { + @Test void returns_a_function_that_takes_a_value_and_returns_a_solve_handler() throws IOException { + final var successSpy = Spy.>lambda(String::length); + final var failureSpy = Spy.lambda(failFunction); - verify(runnableSpy, times(1)).run(); + assertThat(Maybe.partial(successSpy).apply(OK)) + .isInstanceOf(SolveHandler.class) + .extracting(SolveHandler::success, optional(Integer.class)) + .contains(OK.length()); + assertThat(Maybe.partial(failureSpy).apply(OK)) + .isInstanceOf(SolveHandler.class) + .extracting(SolveHandler::error, optional(IOException.class)) + .contains(FAIL_EXCEPTION); + + verify(successSpy, times(1)).apply(OK); + verify(failureSpy, times(1)).apply(OK); } } - @Nested class when_the_operation_fails { - @Test void returns_a_handler_with_the_error() throws IOException { - final var runnableSpy = Spy.lambda(failRunnable); - final var handler = Maybe.fromEffect(runnableSpy); + @Nested class when_a_consumer_is_provided { + @Test void returns_a_function_that_takes_a_value_and_returns_an_effect_handler() throws IOException { + final var successSpy = Spy.>lambda(v -> { }); + final var failureSpy = Spy.lambda(failConsumer); - assertThat(handler.error()).contains(FAIL_EXCEPTION); + assertThat(Maybe.partial(successSpy).apply(OK)) + .isInstanceOf(EffectHandler.class) + .extracting(EffectHandler::error, optional(RuntimeException.class)) + .isEmpty(); + + assertThat(Maybe.partial(failureSpy).apply(OK)) + .isInstanceOf(EffectHandler.class) + .extracting(EffectHandler::error, optional(IOException.class)) + .contains(FAIL_EXCEPTION); - verify(runnableSpy, times(1)).run(); + verify(successSpy, times(1)).accept(OK); + verify(failureSpy, times(1)).accept(OK); } } } @Nested class partialResolver { - @Test void returns_a_function_that_takes_a_value_and_returns_a_resolve_handler() throws IOException { - final var successSpy = Spy.>lambda(String::length); - final var failureSpy = Spy.lambda(failFunction); - - assertThat(Maybe.partialResolver(successSpy).apply(OK)) - .isInstanceOf(ResolveHandler.class) - .extracting(ResolveHandler::success, optional(Integer.class)) - .contains(OK.length()); - assertThat(Maybe.partialResolver(failureSpy).apply(OK)) - .isInstanceOf(ResolveHandler.class) - .extracting(ResolveHandler::error, optional(IOException.class)) - .contains(FAIL_EXCEPTION); - - verify(successSpy, times(1)).apply(OK); - verify(failureSpy, times(1)).apply(OK); + @Test void calls_partial() { + try (var maybe = mockStatic(Maybe.class, CALLS_REAL_METHODS)) { + Maybe.partialResolver(failFunction); + + maybe.verify(() -> Maybe.partial(failFunction)); + } } } @Nested class partialEffect { - @Test void returns_a_function_that_takes_a_value_and_returns_an_effect_handler() throws IOException { - final var successSpy = Spy.>lambda(v -> { }); - final var failureSpy = Spy.lambda(failConsumer); - - assertThat(Maybe.partialEffect(successSpy).apply(OK)) - .isInstanceOf(EffectHandler.class) - .extracting(EffectHandler::error, optional(RuntimeException.class)) - .isEmpty(); - - assertThat(Maybe.partialEffect(failureSpy).apply(OK)) - .isInstanceOf(EffectHandler.class) - .extracting(EffectHandler::error, optional(IOException.class)) - .contains(FAIL_EXCEPTION); + @Test void calls_partial() { + try (var maybe = mockStatic(Maybe.class, CALLS_REAL_METHODS)) { + Maybe.partialEffect(failConsumer); - verify(successSpy, times(1)).accept(OK); - verify(failureSpy, times(1)).accept(OK); + maybe.verify(() -> Maybe.partial(failConsumer)); + } } } @Nested class withResource { - @Test void returns_the_ResourceHolder_with_the_resource() throws FileNotFoundException, IOException { + @Test void returns_the_CloseableHandler_with_the_resource() throws FileNotFoundException, IOException { try (var fis = new FileInputStream("./src/test/resources/readTest.txt")) { final var holder = Maybe.withResource(fis); @@ -197,7 +278,7 @@ @Nested class solveResource { @Nested class and_the_solver_does_not_throw { - @Test void returns_a_ResourceHolder_with_the_resource() throws FileNotFoundException, IOException { + @Test void returns_a_CloseableHandler_with_the_resource() throws FileNotFoundException, IOException { final var path = "./src/test/resources/readTest.txt"; final var holder = Maybe.solveResource(() -> new FileInputStream(path)); @@ -212,7 +293,7 @@ } @Nested class and_the_solver_throws { - @Test void returns_a_ResourceHolder_with_the_thrown_exception() { + @Test void returns_a_CloseableHandler_with_the_thrown_exception() { final var holder = Maybe.solveResource(() -> new FileInputStream("invalid.txt")); assertThat(holder.resource()).isEmpty(); @@ -226,15 +307,15 @@ @Nested class map { @Nested class when_the_value_is_present { @Test void maps_the_value_with_the_passed_function() { - final var maybe = Maybe.just(OK).map(String::length); + final var maybe = Maybe.of(OK).map(String::length); assertThat(maybe.value()).contains(2); } } @Nested class when_the_value_is_not_present { - @Test void returns_nothing() { - final var maybe = Maybe.nothing().map(String::length); + @Test void returns_an_empty_Maybe() { + final var maybe = Maybe.empty().map(String::length); assertThat(maybe.value()).isEmpty(); } @@ -244,28 +325,28 @@ @Nested class flatMap { @Nested class when_the_value_is_present { @Test void maps_the_value_with_the_passed_maybe_function() { - final var maybe = Maybe.just(OK) - .flatMap(str -> Maybe.just(str.length())); + final var maybe = Maybe.of(OK) + .flatMap(str -> Maybe.of(str.length())); assertThat(maybe.value()).contains(2); } } @Nested class when_the_value_is_not_present { - @Test void returns_nothing() { - final var maybe = Maybe.nothing() - .flatMap(str -> Maybe.just(str.length())); + @Test void returns_an_empty_Maybe() { + final var maybe = Maybe.empty() + .flatMap(str -> Maybe.of(str.length())); assertThat(maybe.value()).isEmpty(); } } } - @Nested class resolve { + @Nested class solve { @Nested class when_the_value_is_present { @Test void the_callback_is_called_with_the_value() { final var functionSpy = Spy.>lambda(v -> OK); - final var handler = Maybe.just(1).resolve(functionSpy); + final var handler = Maybe.of(1).solve(functionSpy); assertThat(handler.success()).contains(OK); assertThat(handler.error()).isEmpty(); @@ -277,7 +358,7 @@ @Nested class when_the_value_is_not_present { @Test void the_callback_is_never_called_and_returns_a_handler_with_an_error() throws IOException { final var functionSpy = Spy.lambda(failFunction); - final var handler = Maybe.nothing().resolve(functionSpy); + final var handler = Maybe.empty().solve(functionSpy); assertThat(handler.success()).isEmpty(); assertThat(handler.error()) @@ -290,10 +371,10 @@ } @Nested class when_the_new_operation_succeeds { - @Test void returns_a_handler_with_the_resolved_value() { + @Test void returns_a_handler_with_the_solved_value() { final var functionSpy = Spy.lambda(ThrowingFunction.identity()); - final var handler = Maybe.just(OK) - .resolve(functionSpy); + final var handler = Maybe.of(OK) + .solve(functionSpy); assertThat(handler.success()).contains(OK); assertThat(handler.error()).isEmpty(); @@ -305,8 +386,8 @@ @Nested class when_the_new_operation_fails { @Test void returns_a_handler_with_the_error() throws IOException { final var functionSpy = Spy.lambda(failFunction); - final var handler = Maybe.just(OK) - .resolve(functionSpy); + final var handler = Maybe.of(OK) + .solve(functionSpy); assertThat(handler.success()).isEmpty(); assertThat(handler.error()).contains(FAIL_EXCEPTION); @@ -316,12 +397,21 @@ } } - @Nested class runEffect { + @Nested class resolve { + @Test void calls_solve() { + final var maybe = spy(Maybe.of(OK)); + maybe.resolve(failFunction); + + verify(maybe).solve(failFunction); + } + } + + @Nested class effect { @Nested class when_the_value_is_present { @Test void the_callback_is_called_with_the_value() { final var consumerSpy = Spy.>lambda(v -> { }); - final var handler = Maybe.just(OK) - .runEffect(consumerSpy); + final var handler = Maybe.of(OK) + .effect(consumerSpy); assertThat(handler.error()).isEmpty(); @@ -332,8 +422,8 @@ @Nested class when_the_value_is_not_present { @Test void the_callback_is_never_called_and_returns_a_handler_with_an_error() { final var consumerSpy = Spy.>lambda(v -> { }); - final var handler = Maybe.nothing() - .runEffect(consumerSpy); + final var handler = Maybe.empty() + .effect(consumerSpy); assertThat(handler.error()) .get(THROWABLE) @@ -345,10 +435,10 @@ } @Nested class when_the_new_operation_succeeds { - @Test void returns_the_a_handler_with_nothing() { + @Test void returns_an_empty_handler() { final var consumerSpy = Spy.>lambda(v -> { }); - final var handler = Maybe.just(OK) - .runEffect(consumerSpy); + final var handler = Maybe.of(OK) + .effect(consumerSpy); assertThat(handler.error()).isEmpty(); @@ -359,8 +449,8 @@ @Nested class when_the_new_operation_fails { @Test void returns_a_handler_with_the_error() throws IOException { final var consumerSpy = Spy.lambda(failConsumer); - final var handler = Maybe.just(OK) - .runEffect(consumerSpy); + final var handler = Maybe.of(OK) + .effect(consumerSpy); assertThat(handler.error()).contains(FAIL_EXCEPTION); @@ -369,18 +459,27 @@ } } + @Nested class runEffect { + @Test void call_effect() { + final var maybe = spy(Maybe.of(OK)); + maybe.runEffect(failConsumer); + + verify(maybe).effect(failConsumer); + } + } + @Nested class cast { @Nested class when_the_value_is_castable_to_the_passed_type { @Test void returns_a_maybe_with_the_value_cast() { - final var maybe = Maybe.just(3); + final var maybe = Maybe.of(3); assertThat(maybe.cast(Integer.class).value()).contains(3); } } @Nested class when_the_value_is_not_castable_to_the_passed_type { - @Test void returns_nothing() { - final var maybe = Maybe.just("3"); + @Test void returns_an_empty_Maybe() { + final var maybe = Maybe.of("3"); assertThat(maybe.cast(Integer.class).value()).isEmpty(); } @@ -390,42 +489,51 @@ @Nested class hasValue { @Nested class when_the_value_is_present { @Test void returns_true() { - assertThat(Maybe.just(OK).hasValue()).isTrue(); + assertThat(Maybe.of(OK).hasValue()).isTrue(); } } @Nested class when_the_value_is_not_present { @Test void returns_false() { - assertThat(Maybe.nothing().hasValue()).isFalse(); + assertThat(Maybe.empty().hasValue()).isFalse(); } } } - @Nested class hasNothing { + @Nested class isEmpty { @Nested class when_the_value_is_not_present { @Test void returns_true() { - assertThat(Maybe.nothing().hasNothing()).isTrue(); + assertThat(Maybe.empty().isEmpty()).isTrue(); } } @Nested class when_the_value_is_present { @Test void returns_false() { - assertThat(Maybe.just(OK).hasNothing()).isFalse(); + assertThat(Maybe.of(OK).isEmpty()).isFalse(); } } } + @Nested class hasNothing { + @Test void calls_isEmpty() { + final var maybe = spy(Maybe.of(OK)); + maybe.hasNothing(); + + verify(maybe).isEmpty(); + } + } + @Nested class toOptional { @Nested class when_the_value_is_present { @Test void returns_an_Optional_wrapping_the_value() { - assertThat(Maybe.just(OK).toOptional()) + assertThat(Maybe.of(OK).toOptional()) .contains(OK); } } @Nested class when_the_value_is_not_present { @Test void returns_an_empty_Optional() { - assertThat(Maybe.nothing().toOptional()) + assertThat(Maybe.empty().toOptional()) .isEmpty(); } } @@ -434,7 +542,7 @@ @Nested class equals { @Nested class when_the_tested_object_is_the_same_as_the_value { @Test void returns_true() { - final var maybe = Maybe.just(3); + final var maybe = Maybe.of(3); final var other = maybe; final var isEqual = maybe.equals(other); @@ -444,7 +552,7 @@ @Nested class when_the_tested_object_is_not_the_same_as_the_value { @Test void returns_false() { - final var maybe = Maybe.just(3); + final var maybe = Maybe.of(3); final var other = (Object) Integer.valueOf(3); final var isEqual = maybe.equals(other); @@ -454,8 +562,8 @@ @Nested class when_both_wrapped_values_are_equal { @Test void returns_true() { - final var maybe = Maybe.just(OK); - final var other = Maybe.just(OK); + final var maybe = Maybe.of(OK); + final var other = Maybe.of(OK); final var isEqual = maybe.equals(other); assertThat(isEqual).isTrue(); @@ -464,8 +572,8 @@ @Nested class when_both_wrapped_values_are_not_equal { @Test void returns_false() { - final var maybe = Maybe.just(OK); - final var other = Maybe.just("OTHER"); + final var maybe = Maybe.of(OK); + final var other = Maybe.of("OTHER"); final var isEqualToOther = maybe.equals(other); assertThat(isEqualToOther).isFalse(); @@ -476,7 +584,7 @@ @Nested class hashCode { @Nested class when_the_value_is_present { @Test void returns_the_hash_code_of_the_value() { - final var maybe = Maybe.just(OK); + final var maybe = Maybe.of(OK); assertThat(maybe).hasSameHashCodeAs(OK); } @@ -484,7 +592,7 @@ @Nested class when_the_value_is_not_present { @Test void returns_zero() { - final var maybe = Maybe.nothing(); + final var maybe = Maybe.empty(); assertThat(maybe.hashCode()).isZero(); } @@ -494,17 +602,17 @@ @Nested class toString { @Nested class when_the_value_is_present { @Test void returns_the_string_representation_of_the_value() { - final var maybe = Maybe.just(OK); + final var maybe = Maybe.of(OK); assertThat(maybe).hasToString("Maybe[OK]"); } } @Nested class when_the_value_is_not_present { - @Test void returns_the_string_representation_of_nothing() { - final var maybe = Maybe.nothing(); + @Test void returns_the_string_representation_of_empty() { + final var maybe = Maybe.empty(); - assertThat(maybe).hasToString("Maybe.nothing"); + assertThat(maybe).hasToString("Maybe.empty"); } } } diff --git a/src/test/java/io/github/joselion/maybe/ResolveHandlerTests.java b/src/test/java/io/github/joselion/maybe/SolveHandlerTests.java similarity index 78% rename from src/test/java/io/github/joselion/maybe/ResolveHandlerTests.java rename to src/test/java/io/github/joselion/maybe/SolveHandlerTests.java index 07ce2e5..aa57384 100644 --- a/src/test/java/io/github/joselion/maybe/ResolveHandlerTests.java +++ b/src/test/java/io/github/joselion/maybe/SolveHandlerTests.java @@ -6,6 +6,7 @@ import static org.assertj.core.api.InstanceOfAssertFactories.THROWABLE; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -25,7 +26,7 @@ import io.github.joselion.testing.Spy; import io.github.joselion.testing.UnitTest; -@UnitTest class ResolveHandlerTests { +@UnitTest class SolveHandlerTests { private static final String OK = "OK"; @@ -44,7 +45,7 @@ @Test void calls_the_effect_callback() { final var consumerSpy = Spy.consumer(v -> { }); - Maybe.fromResolver(okOp) + Maybe.from(okOp) .doOnSuccess(consumerSpy); verify(consumerSpy, times(1)).accept(OK); @@ -55,7 +56,7 @@ @Test void never_calls_the_effect_callback() { final var consumerSpy = Spy.consumer(v -> { }); - Maybe.fromResolver(throwingOp) + Maybe.from(throwingOp) .doOnSuccess(consumerSpy); verify(consumerSpy, never()).accept(any()); @@ -70,7 +71,7 @@ @Test void calls_the_effect_callback() { final var consumerSpy = Spy.consumer(error -> { }); - Maybe.fromResolver(throwingOp) + Maybe.from(throwingOp) .doOnError(FileSystemException.class, consumerSpy); verify(consumerSpy, times(1)).accept(FAIL_EXCEPTION); @@ -81,7 +82,7 @@ @Test void never_calls_the_effect_callback() { final var consumerSpy = Spy.consumer(error -> { }); - Maybe.fromResolver(throwingOp) + Maybe.from(throwingOp) .doOnError(RuntimeException.class, consumerSpy); verify(consumerSpy, never()).accept(any()); @@ -93,7 +94,7 @@ @Test void calls_the_effect_callback() { final var consumerSpy = Spy.consumer(error -> { }); - Maybe.fromResolver(throwingOp) + Maybe.from(throwingOp) .doOnError(consumerSpy); verify(consumerSpy, times(1)).accept(FAIL_EXCEPTION); @@ -105,7 +106,7 @@ @Test void never_calls_the_effect_callback() { final var cunsumerSpy = Spy.consumer(error -> { }); - Maybe.fromResolver(okOp) + Maybe.from(okOp) .doOnError(RuntimeException.class, cunsumerSpy) .doOnError(cunsumerSpy); @@ -120,7 +121,7 @@ @Nested class and_the_error_is_an_instance_of_the_provided_type { @Test void calls_the_handler_function() { final var functionSpy = Spy.function((FileSystemException e) -> OK); - final var handler = Maybe.fromResolver(throwingOp) + final var handler = Maybe.from(throwingOp) .catchError(FileSystemException.class, functionSpy); assertThat(handler.success()).contains(OK); @@ -133,7 +134,7 @@ @Nested class and_the_error_is_not_an_instance_of_the_provided_type { @Test void never_calls_the_handler_function() { final var functionSpy = Spy.function((AccessDeniedException e) -> OK); - final var handler = Maybe.fromResolver(throwingOp) + final var handler = Maybe.from(throwingOp) .catchError(AccessDeniedException.class, functionSpy); assertThat(handler.success()).isEmpty(); @@ -147,11 +148,11 @@ @Nested class and_the_error_type_is_not_provided { @Test void calls_the_handler_function() { final var handlerSpy = Spy.function((FileSystemException e) -> OK); - final var resolver = Maybe.fromResolver(throwingOp) + final var solver = Maybe.from(throwingOp) .catchError(handlerSpy); - assertThat(resolver.success()).contains(OK); - assertThat(resolver.error()).isEmpty(); + assertThat(solver.success()).contains(OK); + assertThat(solver.error()).isEmpty(); verify(handlerSpy, times(1)).apply(FAIL_EXCEPTION); } @@ -161,14 +162,14 @@ @Nested class when_the_value_is_present { @Test void never_calls_the_handler_function() { final var functionSpy = Spy.function((RuntimeException e) -> OK); - final var resolvers = List.of( - Maybe.fromResolver(okOp).catchError(RuntimeException.class, functionSpy), - Maybe.fromResolver(okOp).catchError(functionSpy) + final var solvers = List.of( + Maybe.from(okOp).catchError(RuntimeException.class, functionSpy), + Maybe.from(okOp).catchError(functionSpy) ); - assertThat(resolvers).isNotEmpty().allSatisfy(resolver -> { - assertThat(resolver.success()).contains(OK); - assertThat(resolver.error()).isEmpty(); + assertThat(solvers).isNotEmpty().allSatisfy(solver -> { + assertThat(solver.success()).contains(OK); + assertThat(solver.error()).isEmpty(); }); verify(functionSpy, never()).apply(any()); @@ -176,15 +177,15 @@ } } - @Nested class resolve { + @Nested class solve { @Nested class when_the_value_is_present { - @Test void calls_the_resolver_callback_and_returns_a_new_handler() { - final var resolverSpy = Spy.>lambda(String::length); + @Test void calls_the_solver_callback_and_returns_a_new_handler() { + final var solverSpy = Spy.>lambda(String::length); final var successSpy = Spy.>lambda(String::length); final var errorSpy = Spy.>lambda(e -> -1); final var handlers = List.of( - Maybe.fromResolver(okOp).resolve(resolverSpy), - Maybe.fromResolver(okOp).resolve(successSpy, errorSpy) + Maybe.from(okOp).solve(solverSpy), + Maybe.from(okOp).solve(successSpy, errorSpy) ); assertThat(handlers).isNotEmpty().allSatisfy(handler -> { @@ -192,18 +193,18 @@ assertThat(handler.error()).isEmpty(); }); - verify(resolverSpy, times(1)).apply(OK); + verify(solverSpy, times(1)).apply(OK); verify(successSpy, times(1)).apply(OK); verify(errorSpy, never()).apply(any()); } } @Nested class when_the_error_is_present { - @Nested class and_the_error_resolver_is_not_provided { - @Test void never_calls_the_resolver_callback_and_returns_a_handler_with_the_error() { + @Nested class and_the_error_solver_is_not_provided { + @Test void never_calls_the_solver_callback_and_returns_a_handler_with_the_error() { final var successSpy = Spy.>lambda(String::length); - final var handler = Maybe.fromResolver(throwingOp) - .resolve(successSpy); + final var handler = Maybe.from(throwingOp) + .solve(successSpy); assertThat(handler.success()).isEmpty(); assertThat(handler.error()) @@ -215,12 +216,12 @@ } } - @Nested class and_the_error_resolver_is_provided { + @Nested class and_the_error_solver_is_provided { @Test void call_only_the_error_callback_and_returns_a_new_effect_handler() { final var successSpy = Spy.>lambda(String::length); final var errorSpy = Spy.>lambda(e -> -1); - final var handler = Maybe.fromResolver(throwingOp) - .resolve(successSpy, errorSpy); + final var handler = Maybe.from(throwingOp) + .solve(successSpy, errorSpy); assertThat(handler.success()).contains(-1); assertThat(handler.error()).isEmpty(); @@ -232,16 +233,28 @@ } } - @Nested class runEffect { + @Nested class resolve { + @Test void calls_solve() { + final var identity = ThrowingFunction.identity(); + final var handler = spy(Maybe.from(okOp)); + handler.resolve(identity, identity); + handler.resolve(identity); + + verify(handler).solve(identity); + verify(handler).solve(identity, identity); + } + } + + @Nested class effect { @Nested class when_the_value_is_present { - @Test void calls_the_resolver_callback_and_returns_a_new_handler() throws FileSystemException { + @Test void calls_the_solver_callback_and_returns_a_new_handler() throws FileSystemException { final var effectSpy = Spy.>lambda(v -> throwingOp.get()); final var successSpy = Spy.>lambda(v -> throwingOp.get()); final var errorSpy = Spy.>lambda(err -> { }); - final var handler = Maybe.fromResolver(okOp); + final var handler = Maybe.from(okOp); final var newHandlers = List.of( - handler.runEffect(effectSpy), - handler.runEffect(successSpy, errorSpy) + handler.effect(effectSpy), + handler.effect(successSpy, errorSpy) ); assertThat(newHandlers).isNotEmpty().allSatisfy(newHandler -> { @@ -261,8 +274,8 @@ final var errorSpy = Spy.>lambda( err -> throwingOp.get() ); - final var handler = Maybe.fromResolver(throwingOp); - final var newHandler = handler.runEffect(successSpy, errorSpy); + final var handler = Maybe.from(throwingOp); + final var newHandler = handler.effect(successSpy, errorSpy); assertThat(newHandler.error()).contains(FAIL_EXCEPTION); @@ -274,8 +287,8 @@ @Nested class and_the_error_callback_is_not_provided { @Test void never_calls_the_effect_callback_and_returns_a_handler_with_the_error() throws FileSystemException { final var effectSpy = Spy.>lambda(v -> throwingOp.get()); - final var handler = Maybe.fromResolver(throwingOp); - final var newHandler = handler.runEffect(effectSpy); + final var handler = Maybe.from(throwingOp); + final var newHandler = handler.effect(effectSpy); assertThat(newHandler.error()) .get(THROWABLE) @@ -288,10 +301,23 @@ } } + @Nested class runEffect { + @Test void calls_effect() { + final var onSuccess = Spy.>lambda(x -> { }); + final var onError = Spy.>lambda(x -> { }); + final var maybe = spy(Maybe.from(okOp)); + maybe.runEffect(onSuccess); + maybe.runEffect(onSuccess, onError); + + verify(maybe).effect(onSuccess, onError); + verify(maybe).effect(onSuccess); + } + } + @Nested class map { @Nested class when_the_value_is_present { @Test void returns_a_new_handler_applying_the_mapper_function() { - final var handler = ResolveHandler.ofSuccess("Hello world!") + final var handler = SolveHandler.ofSuccess("Hello world!") .map(String::length); assertThat(handler.success()).contains(12); @@ -302,7 +328,7 @@ @Nested class when_the_error_is_present { @Test void returns_a_new_handler_with_the_previous_error() { - final var handler = ResolveHandler.ofError(FAIL_EXCEPTION) + final var handler = SolveHandler.ofError(FAIL_EXCEPTION) .map(Object::toString); assertThat(handler.success()).isEmpty(); @@ -316,7 +342,7 @@ @Nested class and_the_object_can_be_cast { @Test void returns_a_new_handler_with_the_cast_value() { final var anyValue = (Object) "Hello"; - final var handler = ResolveHandler.ofSuccess(anyValue) + final var handler = SolveHandler.ofSuccess(anyValue) .cast(String.class); assertThat(handler.success()).contains("Hello"); @@ -326,7 +352,7 @@ @Nested class and_the_object_can_not_be_cast { @Test void returns_a_new_handler_with_the_cast_exception() { - final var handler = ResolveHandler.ofSuccess(3) + final var handler = SolveHandler.ofSuccess(3) .cast(String.class); assertThat(handler.success()).isEmpty(); @@ -340,7 +366,7 @@ @Nested class when_the_error_is_present { @Test void returns_a_new_handler_with_a_cast_exception() { - final var handler = ResolveHandler.ofError(FAIL_EXCEPTION) + final var handler = SolveHandler.ofError(FAIL_EXCEPTION) .cast(String.class); assertThat(handler.success()).isEmpty(); @@ -355,7 +381,7 @@ @Nested class orElse { @Nested class when_the_value_is_present { @Test void returns_the_value() { - final var handler = Maybe.fromResolver(okOp); + final var handler = Maybe.from(okOp); assertThat(handler.orElse(OTHER)).isEqualTo(OK); assertThat(handler.orElse(RuntimeException::getMessage)).isEqualTo(OK); @@ -364,7 +390,7 @@ @Nested class when_the_error_is_present { @Test void returns_the_provided_value() { - final var handler = Maybe.fromResolver(throwingOp); + final var handler = Maybe.from(throwingOp); assertThat(handler.orElse(OTHER)).isEqualTo(OTHER); assertThat(handler.orElse(FileSystemException::getMessage)).isEqualTo(FAIL_EXCEPTION.getMessage()); @@ -376,7 +402,7 @@ @Nested class when_the_value_is_present { @Test void never_evaluates_the_supplier_and_returns_the_value() { final var supplierSpy = Spy.supplier(() -> OTHER); - final var handler = Maybe.fromResolver(okOp); + final var handler = Maybe.from(okOp); assertThat(handler.orElseGet(supplierSpy)).isEqualTo(OK); @@ -387,7 +413,7 @@ @Nested class when_the_error_is_present { @Test void evaluates_the_supplier_and_returns_the_produced_value() { final var supplierSpy = Spy.supplier(() -> OTHER); - final var handler = Maybe.fromResolver(throwingOp); + final var handler = Maybe.from(throwingOp); assertThat(handler.orElseGet(supplierSpy)).isEqualTo(OTHER); @@ -399,7 +425,7 @@ @Nested class orNull { @Nested class when_the_value_is_present { @Test void returns_the_value() { - final var handler = Maybe.fromResolver(okOp); + final var handler = Maybe.from(okOp); assertThat(handler.orNull()).isEqualTo(OK); } @@ -407,7 +433,7 @@ @Nested class when_the_error_is_present { @Test void returns_null() { - final var handler = Maybe.fromResolver(throwingOp); + final var handler = Maybe.from(throwingOp); assertThat(handler.orNull()).isNull(); } @@ -418,7 +444,7 @@ @Nested class when_the_value_is_present { @Test void returns_the_value() throws FileSystemException { final var functionSpy = Spy.function((RuntimeException error) -> FAIL_EXCEPTION); - final var handler = Maybe.fromResolver(okOp); + final var handler = Maybe.from(okOp); assertThat(handler.orThrow()).isEqualTo(OK); assertThat(handler.orThrow(functionSpy)).isEqualTo(OK); @@ -431,7 +457,7 @@ @Test void throws_the_error() { final var anotherError = new RuntimeException(OTHER); final var functionSpy = Spy.function((FileSystemException error) -> anotherError); - final var handler = Maybe.fromResolver(throwingOp); + final var handler = Maybe.from(throwingOp); assertThatThrownBy(handler::orThrow).isEqualTo(FAIL_EXCEPTION); assertThatThrownBy(() -> handler.orThrow(functionSpy)).isEqualTo(anotherError); @@ -444,14 +470,14 @@ @Nested class toMaybe { @Nested class when_the_value_is_present { @Test void returns_a_maybe_with_the_value() { - assertThat(Maybe.fromResolver(okOp).toMaybe().value()) + assertThat(Maybe.from(okOp).toMaybe().value()) .contains(OK); } } @Nested class when_the_error_is_present { - @Test void returns_a_maybe_with_nothing() { - assertThat(Maybe.fromResolver(throwingOp).toMaybe().value()) + @Test void returns_an_empty_Maybe_() { + assertThat(Maybe.from(throwingOp).toMaybe().value()) .isEmpty(); } } @@ -460,13 +486,13 @@ @Nested class toOptional { @Nested class when_the_value_is_present { @Test void returns_the_value_wrapped_in_an_optinal() { - assertThat(Maybe.fromResolver(okOp).toOptional()).contains(OK); + assertThat(Maybe.from(okOp).toOptional()).contains(OK); } } @Nested class when_the_error_is_present { @Test void returns_an_empty_optional() { - assertThat(Maybe.fromResolver(throwingOp).toOptional()).isEmpty(); + assertThat(Maybe.from(throwingOp).toOptional()).isEmpty(); } } } @@ -474,7 +500,7 @@ @Nested class toEither { @Nested class when_the_value_is_present { @Test void returns_an_Either_with_the_value_on_its_right_side() { - final var either = Maybe.fromResolver(okOp).toEither(); + final var either = Maybe.from(okOp).toEither(); assertThat(either.isLeft()).isFalse(); assertThat(either.isRight()).isTrue(); @@ -485,7 +511,7 @@ @Nested class when_the_error_is_present { @Test void returns_an_Either_with_the_error_on_its_left_side() { - final var either = Maybe.fromResolver(throwingOp).toEither(); + final var either = Maybe.from(throwingOp).toEither(); assertThat(either.isLeft()).isTrue(); assertThat(either.isRight()).isFalse(); @@ -499,8 +525,8 @@ @Nested class when_the_resource_is_present { @Test void returns_a_resource_holder_with_the_mapped_value() { final var path = "./src/test/resources/readTest.txt"; - final var holder = Maybe.just(path) - .resolve(FileInputStream::new) + final var holder = Maybe.of(path) + .solve(FileInputStream::new) .mapToResource(Function.identity()); assertThat(holder.resource()) @@ -512,8 +538,8 @@ @Nested class when_the_error_is_present { @Test void returns_a_resource_holder_with_the_propagated_error() { - final var holder = Maybe.just("invalidFile.txt") - .resolve(FileInputStream::new) + final var holder = Maybe.of("invalidFile.txt") + .solve(FileInputStream::new) .mapToResource(Function.identity()); assertThat(holder.resource()).isEmpty(); @@ -528,11 +554,11 @@ @Nested class solveResource { @Nested class when_the_value_is_present { @Nested class and_the_solver_does_not_throw { - @Test void returns_a_ResourceHolder_with_the_resource() { + @Test void returns_a_CloseableHandler_with_the_resource() { final var path = "./src/test/resources/readTest.txt"; final var holder = Maybe - .just(path) - .resolve(ThrowingFunction.identity()) + .of(path) + .solve(ThrowingFunction.identity()) .solveResource(FileInputStream::new); assertThat(holder.resource()) @@ -546,10 +572,10 @@ } @Nested class and_the_solver_throws { - @Test void returns_a_ResourceHolder_with_the_thrown_exception() { + @Test void returns_a_CloseableHandler_with_the_thrown_exception() { final var holder = Maybe - .just("invalid.txt") - .resolve(ThrowingFunction.identity()) + .of("invalid.txt") + .solve(ThrowingFunction.identity()) .solveResource(FileInputStream::new); assertThat(holder.resource()).isEmpty(); @@ -561,9 +587,9 @@ } @Nested class when_the_error_is_present { - @Test void returns_a_ResourceHolder_with_the_propagated_error() { + @Test void returns_a_CloseableHandler_with_the_propagated_error() { final var holder = Maybe - .fromResolver(throwingOp) + .from(throwingOp) .solveResource(FileInputStream::new); assertThat(holder.resource()).isEmpty();