Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(autoclosable): Add solveResource operation #216

Merged
merged 1 commit into from
Nov 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 32 additions & 10 deletions src/main/java/io/github/joselion/maybe/Maybe.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package io.github.joselion.maybe;

import java.io.Closeable;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.function.Function;

import org.eclipse.jdt.annotation.Nullable;

import io.github.joselion.maybe.helpers.Common;
import io.github.joselion.maybe.util.function.ThrowingConsumer;
import io.github.joselion.maybe.util.function.ThrowingFunction;
import io.github.joselion.maybe.util.function.ThrowingRunnable;
Expand Down Expand Up @@ -97,8 +99,7 @@ public static <T, E extends Throwable> ResolveHandler<T, E> fromResolver(final T
try {
return ResolveHandler.ofSuccess(resolver.get());
} catch (Throwable e) { // NOSONAR
@SuppressWarnings("unchecked")
final var error = (E) e;
final var error = Common.<E>cast(e);
return ResolveHandler.ofError(error);
}
}
Expand All @@ -118,8 +119,7 @@ public static <E extends Throwable> EffectHandler<E> fromEffect(final ThrowingRu
effect.run();
return EffectHandler.empty();
} catch (Throwable e) { // NOSONAR
@SuppressWarnings("unchecked")
final var error = (E) e;
final var error = Common.<E>cast(e);
return EffectHandler.ofError(error);
}
}
Expand Down Expand Up @@ -201,6 +201,28 @@ public static <R extends AutoCloseable, E extends Throwable> ResourceHolder<R, E
return ResourceHolder.from(resource);
}

/**
* Prepare an {@link AutoCloseable} resource to use in a resolver or effect,
* using a {@link ThrowingSupplier}. Any exception thrown by the supplier
* will be propageted to the {@link ResourceHolder}. The resource will be
* automatically closed after the operation is finished, just like a common
* try-with-resources statement.
*
* @param <R> the type of the resource. Extends from {@link AutoCloseable}
* @param <E> 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
* or run an effect using the prepared resource
*/
public static <R extends Closeable, E extends Throwable> ResourceHolder<R, E> solveResource(
final ThrowingSupplier<R, E> supplier
) {
return Maybe
.fromResolver(supplier)
.map(ResourceHolder::<R, E>from)
.orElse(ResourceHolder::failure);
}

/**
* If present, maps the value to another using the provided mapper function.
* Otherwise, ignores the mapper and returns {@link #nothing()}.
Expand Down Expand Up @@ -246,14 +268,14 @@ public <U> Maybe<U> flatMap(final Function<T, Maybe<U>> mapper) {
* @return a {@link ResolveHandler} with either the resolved value, or the
* thrown exception to be handled
*/
@SuppressWarnings("unchecked")
public <U, E extends Throwable> ResolveHandler<U, E> resolve(final ThrowingFunction<T, U, E> resolver) {
try {
return value
.map(Maybe.partialResolver(resolver))
.orElseThrow();
} catch (final NoSuchElementException error) {
return ResolveHandler.ofError((E) error);
} catch (final NoSuchElementException e) {
final var error = Common.<E>cast(e);
return ResolveHandler.ofError(error);
}
}

Expand All @@ -266,14 +288,14 @@ public <U, E extends Throwable> ResolveHandler<U, E> resolve(final ThrowingFunct
* @return an {@link EffectHandler} with either the thrown exception to be
* handled or nothing
*/
@SuppressWarnings("unchecked")
public <E extends Throwable> EffectHandler<E> runEffect(final ThrowingConsumer<T, E> effect) {
try {
return value
.map(Maybe.partialEffect(effect))
.orElseThrow();
} catch (final NoSuchElementException error) {
return EffectHandler.ofError((E) error);
} catch (final NoSuchElementException e) {
final var error = Common.<E>cast(e);
return EffectHandler.ofError(error);
}
}

Expand Down
99 changes: 66 additions & 33 deletions src/main/java/io/github/joselion/maybe/ResolveHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import org.eclipse.jdt.annotation.Nullable;

import io.github.joselion.maybe.helpers.Common;
import io.github.joselion.maybe.util.Either;
import io.github.joselion.maybe.util.function.ThrowingConsumer;
import io.github.joselion.maybe.util.function.ThrowingFunction;
Expand Down Expand Up @@ -60,7 +61,7 @@ static <T, E extends Throwable> ResolveHandler<T, E> ofError(final E error) {
* @return the possible success value
*/
Optional<T> success() {
return value.rightToOptional();
return this.value.rightToOptional();
}

/**
Expand All @@ -69,7 +70,7 @@ Optional<T> success() {
* @return the possible thrown exception
*/
Optional<E> error() {
return value.leftToOptional();
return this.value.leftToOptional();
}

/**
Expand All @@ -80,7 +81,7 @@ Optional<E> error() {
* @return the same handler to continue chainning operations
*/
public ResolveHandler<T, E> doOnSuccess(final Consumer<T> effect) {
value.doOnRight(effect);
this.value.doOnRight(effect);

return this;
}
Expand All @@ -96,7 +97,7 @@ public ResolveHandler<T, E> doOnSuccess(final Consumer<T> effect) {
* @return the same handler to continue chainning operations
*/
public <X extends Throwable> ResolveHandler<T, E> doOnError(final Class<X> ofType, final Consumer<X> effect) {
value.leftToOptional()
this.value.leftToOptional()
.filter(ofType::isInstance)
.map(ofType::cast)
.ifPresent(effect);
Expand All @@ -112,7 +113,7 @@ public <X extends Throwable> ResolveHandler<T, E> doOnError(final Class<X> ofTyp
* @return the same handler to continue chainning operations
*/
public ResolveHandler<T, E> doOnError(final Consumer<E> effect) {
value.doOnLeft(effect);
this.value.doOnLeft(effect);

return this;
}
Expand All @@ -130,7 +131,7 @@ public ResolveHandler<T, E> doOnError(final Consumer<E> effect) {
* provided type was caught. The same handler instance otherwise
*/
public <X extends E> ResolveHandler<T, E> catchError(final Class<X> ofType, final Function<X, T> handler) {
return value
return this.value
.leftToOptional()
.filter(ofType::isInstance)
.map(ofType::cast)
Expand All @@ -149,7 +150,7 @@ public <X extends E> ResolveHandler<T, E> catchError(final Class<X> ofType, fina
* handler instance otherwise
*/
public ResolveHandler<T, E> catchError(final Function<E, T> handler) {
return value
return this.value
.mapLeft(handler)
.mapLeft(ResolveHandler::<T, E>ofSuccess)
.leftOrElse(this);
Expand All @@ -176,7 +177,7 @@ public <S, X extends Throwable> ResolveHandler<S, X> resolve(
final ThrowingFunction<T, S, X> onSuccess,
final ThrowingFunction<E, S, X> onError
) {
return value.unwrap(
return this.value.unwrap(
Maybe.partialResolver(onError),
Maybe.partialResolver(onSuccess)
);
Expand All @@ -192,12 +193,13 @@ public <S, X extends Throwable> ResolveHandler<S, X> resolve(
* resolves another
* @return a new handler with either the resolved value or an error
*/
@SuppressWarnings("unchecked")
public <S, X extends Throwable> ResolveHandler<S, X> resolve(final ThrowingFunction<T, S, X> resolver) {
return value.unwrap(
error -> ResolveHandler.ofError((X) error),
Maybe.partialResolver(resolver)
);
return this.value
.mapLeft(Common::<X>cast)
.unwrap(
ResolveHandler::ofError,
Maybe.partialResolver(resolver)
);
}

/**
Expand All @@ -214,7 +216,7 @@ public <X extends Throwable> EffectHandler<X> runEffect(
final ThrowingConsumer<T, X> onSuccess,
final ThrowingConsumer<E, X> onError
) {
return value.unwrap(
return this.value.unwrap(
Maybe.partialEffect(onError),
Maybe.partialEffect(onSuccess)
);
Expand All @@ -230,12 +232,13 @@ public <X extends Throwable> EffectHandler<X> runEffect(
* @return a new {@link EffectHandler} representing the result of the success
* callback or containg the error
*/
@SuppressWarnings("unchecked")
public <X extends Throwable> EffectHandler<X> runEffect(final ThrowingConsumer<T, X> effect) {
return value.unwrap(
error -> EffectHandler.ofError((X) error),
Maybe.partialEffect(effect)
);
return this.value
.mapLeft(Common::<X>cast)
.unwrap(
EffectHandler::ofError,
Maybe.partialEffect(effect)
);
}

/**
Expand All @@ -248,7 +251,7 @@ public <X extends Throwable> EffectHandler<X> runEffect(final ThrowingConsumer<T
* @return a new handler with either the mapped value, or the previous error
*/
public <U> ResolveHandler<U, E> map(final Function<T, U> mapper) {
return value
return this.value
.mapRight(mapper)
.unwrap(
ResolveHandler::ofError,
Expand All @@ -267,7 +270,7 @@ public <U> ResolveHandler<U, E> map(final Function<T, U> mapper) {
* error
*/
public <U> ResolveHandler<U, ClassCastException> cast(final Class<U> type) {
return value.unwrap(
return this.value.unwrap(
error -> ofError(new ClassCastException(error.getMessage())),
success -> {
try {
Expand All @@ -286,7 +289,7 @@ public <U> ResolveHandler<U, ClassCastException> cast(final Class<U> type) {
* @return the resolved value if present. Another value otherwise
*/
public T orElse(final T fallback) {
return value.rightOrElse(fallback);
return this.value.rightOrElse(fallback);
}

/**
Expand All @@ -299,7 +302,7 @@ public T orElse(final T fallback) {
* @return the resolved value if present. Another value otherwise
*/
public T orElse(final Function<E, T> mapper) {
return value.unwrap(mapper, Function.identity());
return this.value.unwrap(mapper, Function.identity());
}

/**
Expand All @@ -314,7 +317,7 @@ public T orElse(final Function<E, T> mapper) {
* @return the resolved value if present. Another value otherwise
*/
public T orElseGet(final Supplier<T> supplier) {
return value
return this.value
.rightToOptional()
.orElseGet(supplier);
}
Expand All @@ -332,7 +335,7 @@ public T orElseGet(final Supplier<T> supplier) {
* @return the resolved value if present. Just {@code null} otherwise.
*/
public @Nullable T orNull() {
return value.rightOrNull();
return this.value.rightOrNull();
}

/**
Expand All @@ -342,9 +345,9 @@ public T orElseGet(final Supplier<T> supplier) {
* @throws E the error thrown by the {@code resolve} operation
*/
public T orThrow() throws E {
return value
return this.value
.rightToOptional()
.orElseThrow(value::leftOrNull);
.orElseThrow(this.value::leftOrNull);
}

/**
Expand All @@ -357,9 +360,9 @@ public T orThrow() throws E {
* @throws X a mapped exception
*/
public <X extends Throwable> T orThrow(final Function<E, X> mapper) throws X {
return value
return this.value
.rightToOptional()
.orElseThrow(() -> mapper.apply(value.leftOrNull()));
.orElseThrow(() -> mapper.apply(this.value.leftOrNull()));
}

/**
Expand All @@ -369,7 +372,7 @@ public <X extends Throwable> T orThrow(final Function<E, X> mapper) throws X {
* @return the resolved value wrapped in a {@link Maybe} or holding the error
*/
public Maybe<T> toMaybe() {
return value
return this.value
.rightToOptional()
.map(Maybe::just)
.orElseGet(Maybe::nothing);
Expand All @@ -384,7 +387,7 @@ public Maybe<T> toMaybe() {
* {@code empty} optional otherwise.
*/
public Optional<T> toOptional() {
return value.rightToOptional();
return this.value.rightToOptional();
}

/**
Expand All @@ -399,7 +402,7 @@ public Optional<T> toOptional() {
* error on the left
*/
public Either<E, T> toEither() {
return value;
return this.value;
}

/**
Expand All @@ -418,11 +421,41 @@ public Either<E, T> toEither() {
* @see ResourceHolder#runEffectClosing(ThrowingConsumer)
*/
public <R extends AutoCloseable> ResourceHolder<R, E> mapToResource(final Function<T, R> mapper) {
return value
return this.value
.mapRight(mapper)
.unwrap(
ResourceHolder::failure,
ResourceHolder::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}.
*
* @param <R> the type of the {@link AutoCloseable} resource
* @param <X> 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
* present or the error otherwise.
*/
public <R extends AutoCloseable, X extends Throwable> ResourceHolder<R, X> solveResource(
final ThrowingFunction<T, R, X> solver
) {
return this.value
.mapLeft(Common::<X>cast)
.unwrap(
ResourceHolder::failure,
prev ->
Maybe
.just(prev)
.resolve(solver)
.map(ResourceHolder::<R, X>from)
.catchError(ResourceHolder::failure)
.orElse(ResourceHolder::failure)
);
}
}
Loading