Closing.java
/*-
* #%L
* io.earcam.unexceptional
* %%
* Copyright (C) 2016 - 2017 earcam
* %%
* SPDX-License-Identifier: (BSD-3-Clause OR EPL-1.0 OR Apache-2.0 OR MIT)
*
* You <b>must</b> choose to accept, in full - any individual or combination of
* the following licenses:
* <ul>
* <li><a href="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause</a></li>
* <li><a href="https://www.eclipse.org/legal/epl-v10.html">EPL-1.0</a></li>
* <li><a href="https://www.apache.org/licenses/LICENSE-2.0">Apache-2.0</a></li>
* <li><a href="https://opensource.org/licenses/MIT">MIT</a></li>
* </ul>
* #L%
*/
package io.earcam.unexceptional;
import static io.earcam.unexceptional.Exceptional.uncheck;
import java.io.IOException;
import java.util.function.Supplier;
/**
* <p>
* Succinctly handle {@link AutoCloseable}s that throw checked {@link Exception}s.
* </p>
*
* <p>
* There are two families of static methods; {@code closeAfterApplying(...)} and {@code closeAfterAccepting(...)}, the
* former <i>applies</i> checked functions (the use-case being <b>read</b>) while the later <i>accepts</i> checked
* consumers (the use-case being <b>write</b>)
* </p>
*
* <p>
* <b>Example</b>: this zero-branch, single-instruction, one-liner will find a free port number:
* </p>
*
* <p>
* <code> int port = Closing.closeAfterApplying(ServerSocket::new, 0, ServerSocket::getLocalPort);</code>
* </p>
*
* <p>
* <b>Motivation</b>: I/O stream instances typically (and necessarily) requires handling a lot of possible
* {@link IOException}s - by invoking:
* </p>
* <ul>
* <li>Constructor</li>
* <li>A read/write call</li>
* <li>A call to {@link AutoCloseable#close()}</li>
* <li>A read/write call, then subsequently in the call to {@link AutoCloseable#close()} - via
* {@code try}-with-resources</li>
* </ul>
*
* <p>
* That's a lot of branches to cover - often it is irrelevant to the application which branch actually throws, but
* regardless test coverage suffers without some needless stub/mocked throw-on-create/read/write/close tests.
* </p>
*
* @since 0.3.0
*/
public final class Closing {
private Closing()
{
throw new IllegalStateException("Why on earth would you want to instantiate this?");
}
/**
* <p>
* Ultra-shorthand for {@link AutoCloseable}/{@link java.io.Closeable}, obvious use for {@link java.io.InputStream}
* </p>
*
* @param create a function applying {@code t} to produce an {@link AutoCloseable} of type {@code <C>}
* @param t the argument to apply to the {@code create} function
* @param convert a function applied to the {@link AutoCloseable} to produce the result
*
* @param <C> {@link AutoCloseable} type
* @param <T> {@code create} function argument type
* @param <R> the result type
*
* @return the result of applying the {@code convert} function
*/
public static <C extends AutoCloseable, T, R> R closeAfterApplying(CheckedFunction<T, C, ?> create, T t, CheckedFunction<C, R, ?> convert)
{
C closeable = Exceptional.apply(create, t);
return closeAfterApplying(closeable, convert);
}
/**
* Applies the function to the closeable, returning the result and closing the closable - checked exceptions are
* rethrown as unchecked.
*
* @param closeable the closeable subject of the {@code convert} function
* @param convert the function consuming the closeable and supplying the result
*
* @param <C> the auto-closeable type, to apply and close
* @param <R> the result type
*
* @return the result of applying {@code convert} function to the {@code closeable} argument
*/
public static <C extends AutoCloseable, R> R closeAfterApplying(C closeable, CheckedFunction<C, R, ?> convert)
{
try(C autoClose = closeable) {
return Exceptional.apply(convert, autoClose);
} catch(Exception e) {
throw uncheck(e);
}
}
/**
* Applies the bi-function to the closeable, returning the result and closing the closable - checked exceptions are
* rethrown as unchecked.
*
* @param closeable the closeable subject of the {@code convert} bi-function
* @param instance the second argument for the bi-function
* @param convert the function consuming the closeable and supplying the result
*
* @param <C> the auto-closeable type, will be applied and closed
* @param <U> the type of second argument to bi-function apply
* @param <R> the result type
*
* @return the result of applying {@code convert} function to the {@code closeable} argument
*/
public static <C extends AutoCloseable, U, R, E extends Throwable> R closeAfterApplying(C closeable, U instance, CheckedBiFunction<C, U, R, E> convert)
{
try(C autoClose = closeable) {
return Exceptional.apply(convert, autoClose, instance);
} catch(Exception e) {
throw uncheck(e);
}
}
/**
* Applies the {@code create} function to {@code t}, resulting in a {@link AutoCloseable} which is closed after
* being consumed.
* Checked exceptions are rethrown as unchecked.
*
* @param create the function creating the {@link AutoCloseable}
* @param t the argument that the {@code create} function is applied to
* @param consume the consumer of the {@link AutoCloseable}
*
* @param <C> the auto-closeable type, to be created, consumed and closed
* @param <T> the function's argument type, used to create the auto-closeable
*/
public static <C extends AutoCloseable, T> void closeAfterAccepting(CheckedFunction<T, C, ?> create, T t, CheckedConsumer<C, ?> consume)
{
C closeable = Exceptional.apply(create, t);
closeAfterAccepting(closeable, consume);
}
/**
* Consumes the {@code closeable} before closing. Checked exceptions are rethrown as unchecked.
*
* @param closeable the closeable to be consumed and closed
* @param consume the consumer of the {@link AutoCloseable}
*
* @param <C> the auto-closeable type
*/
public static <C extends AutoCloseable> void closeAfterAccepting(C closeable, CheckedConsumer<C, ?> consume)
{
try(C autoClose = closeable) {
Exceptional.accept(consume, autoClose);
} catch(Exception e) {
throw uncheck(e);
}
}
public static <C extends AutoCloseable, T, U> void closeAfterAccepting(CheckedFunction<T, C, ?> create, T t, U instance, CheckedBiConsumer<C, U, ?> consume)
{
C closeable = Exceptional.apply(create, t);
closeAfterAccepting(closeable, instance, consume);
}
/**
* Consumes both the {@code closeable} and {@code instance} before closing. Checked exceptions are rethrown as
* unchecked.
*
* @param closeable the closeable to be consumed and closed
* @param instance the instance to consume
* @param consume the consumer of the {@link AutoCloseable}
*
* @param <C> the auto-closeable type to consume
* @param <U> the type of consumer's second argument
*/
public static <C extends AutoCloseable, U> void closeAfterAccepting(C closeable, U instance, CheckedBiConsumer<C, U, ?> consume)
{
try(C autoClose = closeable) {
Exceptional.accept(consume, autoClose, instance);
} catch(Exception e) {
throw uncheck(e);
}
}
public static class AutoClosed<T, E extends Exception> implements AutoCloseable, Supplier<T> {
private final T instance;
private final CheckedConsumer<T, E> closeMethod;
AutoClosed(T instance, CheckedConsumer<T, E> closeMethod)
{
this.instance = instance;
this.closeMethod = closeMethod;
}
@Override
public T get()
{
return instance;
}
@Override
public void close() throws E
{
closeMethod.accept(instance);
}
}
/**
* Reshape instances to fit try-catch autoclose.
*
* Example usage:
* <code>
* try(AutoClosed<VirtualMachine, IOException> vm = Closing.autoClosing(getCurrentVm(), VirtualMachine::detach)) {
* vm.get().loadAgent(x, y);
* }
* </code>
*
* @param instance
* @param closeMethod
* @return
*
* @since 0.5.0
*/
public static <T, E extends Exception> AutoClosed<T, E> autoClosing(T instance, CheckedConsumer<T, E> closeMethod)
{
return new AutoClosed<>(instance, closeMethod);
}
}