Created
November 6, 2021 09:48
Functional-style resource construction/consumption/destruction with checked exception handling
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package io; | |
import java.util.function.BiFunction; | |
import java.util.function.Function; | |
/** | |
* Instances of this class are used for handling {@link ResourceConstructor construction}, | |
* {@link Res#applyAndDispose(Function) consumption with automatic destruction} and | |
* {@link Res#apply(BiFunction)} consumption with arranged destruction} | |
* of resources. They encapsulate logic to destruct resources and wrap checked | |
* exceptions thrown during construction and/or destruction with unchecked. | |
* | |
* @param <SupR> the common supertype of resources handled by the instance of this class | |
* @param <X> the type of checked exception thrown by either construction or destruction | |
* of resources or by both, handled by the instance of this class. | |
* @author peter.levart@gmail.com | |
*/ | |
public final class Try<SupR, X extends Throwable> { | |
private final ResourceDestructor<? super SupR, ? extends X> resourceDestructor; | |
private final Function<? super X, ? extends RuntimeException> checkedExceptionWrapper; | |
/** | |
* Constructs an instance of {@link Try} that can be (re)used in functional-style | |
* expressions that construct resources of type {@code SupR} or any subtype, | |
* consume the resources and have a common way of destructing such resources. | |
* | |
* @param resourceDestructor the function that is used to destruct resources | |
* of common type {@code SupR} | |
* @param checkedExceptionWrapper the function that wraps checked exceptions thrown | |
* during resource construction and/or destruction | |
* with unchecked runtime exceptions | |
*/ | |
public Try( | |
ResourceDestructor<? super SupR, ? extends X> resourceDestructor, | |
Function<? super X, ? extends RuntimeException> checkedExceptionWrapper | |
) { | |
this.resourceDestructor = resourceDestructor; | |
this.checkedExceptionWrapper = checkedExceptionWrapper; | |
} | |
/** | |
* Like {@link #Try(ResourceDestructor, Function)} but takes an additional | |
* {@code checkedExceptionType} parameter used just as a hint to compiler | |
* when such type can't be inferred from parameters or target type. | |
* | |
* @param checkedExceptionType the type of checked exceptions thrown by resource | |
* construction and/or destruction | |
* @param resourceDestructor the function that is used to destruct resources | |
* of common type {@code SupR} | |
* @param checkedExceptionWrapper the function that wraps checked exceptions thrown | |
* during resource construction and/or destruction | |
* with unchecked runtime exceptions | |
*/ | |
public Try( | |
Class<X> checkedExceptionType, /* used just as a hint to compiler */ | |
ResourceDestructor<? super SupR, ? extends X> resourceDestructor, | |
Function<? super X, ? extends RuntimeException> checkedExceptionWrapper | |
) { | |
this(resourceDestructor, checkedExceptionWrapper); | |
} | |
/** | |
* Takes a {@code resourceConstructor} function and invokes it to construct | |
* a resource of type {@code R}, wrapping any checked exception of type {@code X} | |
* with unchecked exception and returns a {@link Res Res<R>} instance wrapping | |
* the resource and representing a "close-debt". | |
* | |
* @param resourceConstructor the function that is invoked to construct a resource | |
* of type {@code R} | |
* @param <R> the type of resource to be constructed | |
* @return Res<R> instance wrapping the resource and representing a "close-debt" | |
* @see Res#applyAndDispose(Function) | |
* @see Res#apply(BiFunction) | |
*/ | |
public <R extends SupR> Try<SupR, X>.Res<R> with( | |
ResourceConstructor<? extends R, ? extends X> resourceConstructor | |
) { | |
try { | |
return new Res<>(resourceConstructor.construct()); | |
} catch (RuntimeException | Error e) { | |
throw e; | |
} catch (Throwable e) { | |
@SuppressWarnings("unchecked") X x = (X) e; | |
throw checkedExceptionWrapper.apply(x); | |
} | |
} | |
/** | |
* Inner class wrapper for resources of type {@code R} representing a | |
* "close-debt" by delegating to outer {@link Try} instance for resource | |
* destruction and checked exception wrapping. | |
* | |
* @param <R> the type of wrapped resource | |
*/ | |
public final class Res<R extends SupR> { | |
private final R resource; | |
private Res(R resource) { | |
this.resource = resource; | |
} | |
/** | |
* Applies given function to the wrapped resource and then disposes the | |
* resource by delegating to outer {@link Try} instance encapsulating | |
* resource destructor function, wrapping any checked exception thrown | |
* during resource destruction with unchecked. | |
* | |
* @param fn the function to apply to the wrapped resource of type {@code R} | |
* @param <T> the function return type | |
* @return the value returned by given function | |
*/ | |
public <T> T applyAndDispose( | |
Function<? super R, ? extends T> fn | |
) { | |
Throwable ex = null; | |
try { | |
return fn.apply(resource); | |
} catch (Throwable e1) { | |
ex = e1; | |
} finally { | |
try { | |
resourceDestructor.destruct(resource); | |
} catch (RuntimeException | Error e2) { | |
if (ex == null) { | |
ex = e2; | |
} else { | |
ex.addSuppressed(e2); | |
} | |
} catch (Throwable e3) { | |
if (ex == null) { | |
@SuppressWarnings("unchecked") X x = (X) e3; | |
ex = checkedExceptionWrapper.apply(x); | |
} else { | |
ex.addSuppressed(e3); | |
} | |
} | |
} | |
if (ex instanceof RuntimeException rtex) { | |
throw rtex; | |
} else { | |
throw (Error) ex; | |
} | |
} | |
/** | |
* Applies given function to the wrapped resource but instead of disposing | |
* the resource afterwards, it passes a {@link Runnable} callback to the | |
* function so that it can arrange for it to be called in order to | |
* dispose the resource and wrap any checked exception thrown during disposing | |
* with unchecked. | |
* | |
* @param fn the function to apply to the wrapped resource of type {@code R} | |
* and to arrange for the {@link Runnable} callback to be called to | |
* dispose the resource | |
* @param <T> the function return type | |
* @return the value returned by given function | |
*/ | |
public <T> T apply( | |
BiFunction<? super R, Runnable, ? extends T> fn | |
) { | |
return fn.apply( | |
resource, | |
() -> { | |
try { | |
resourceDestructor.destruct(resource); | |
} catch (RuntimeException | Error e1) { | |
throw e1; | |
} catch (Throwable e2) { | |
@SuppressWarnings("unchecked") X x = (X) e2; | |
throw checkedExceptionWrapper.apply(x); | |
} | |
} | |
); | |
} | |
} | |
/** | |
* Resource constructor functional interface. | |
* | |
* @param <R> the type of resource constructed by the function | |
* @param <X> the type of checked exception thrown by the function | |
*/ | |
@FunctionalInterface | |
public interface ResourceConstructor<R, X extends Throwable> { | |
R construct() throws X; | |
} | |
/** | |
* Resource destructor functional interface. | |
* | |
* @param <R> the type of resource destructed by the function | |
* @param <X> the type of checked exception thrown by the function | |
*/ | |
@FunctionalInterface | |
public interface ResourceDestructor<R, X extends Throwable> { | |
void destruct(R resource) throws X; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment