Skip to content

Instantly share code, notes, and snippets.

@plevart
Created November 6, 2021 09:48
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save plevart/c26e9908573d4a28c709b7218b001ea8 to your computer and use it in GitHub Desktop.
Save plevart/c26e9908573d4a28c709b7218b001ea8 to your computer and use it in GitHub Desktop.
Functional-style resource construction/consumption/destruction with checked exception handling
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&lt;R&gt;} 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&lt;R&gt; 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