
org.hibernate.search.util.impl.Closer Maven / Gradle / Ivy
Show all versions of hibernate-search-engine Show documentation
/*
* Hibernate Search, full-text search for your domain model
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or .
*/
package org.hibernate.search.util.impl;
/**
* A helper for closing multiple resources and re-throwing only one exception,
* {@link Throwable#addSuppressed(Throwable) suppressing} the others as necessary.
*
* This class is not thread safe.
*
* This helper is mainly useful when implementing {@link AutoCloseable#close()}
* or a similar closing method in your own class, to make sure that all resources are
* at least given the chance to close, even if closing one of them fails.
* When creating then closing resources in the scope of a single method call,
* try-with-resource blocks should be favored.
*
* Warning: In order not to ignore exceptions,
* you should always call {@link Closer#close()}
* once you closed all of your resources. The most straightforward way
* to do this is to use a try-with-resource block:
*
* public void myCloseFunction() throws MyException {
* try ( Closer<MyException> closer = new Closer<>() ) {
* closer.push( this.myCloseable::close );
* closer.pushAll( this.myListOfCloseables, MyCloseableType::close );
* }
* }
*
*
* Exception type
*
* Note that the closer has a generic type parameter, allowing it to
* re-throw a given checked exception.
* If you don't want to use this feature, you can simply use a
* {@code Closer}.
*
* Splitting
*
* If you need to close multiple resources throwing different checked
* exceptions, and those exceptions don't have a practical common superclass,
* you can "split" the closer:
*
* public void myCloseFunction() throws IOException, MyException, MyOtherException {
* try ( Closer<MyException> closer1 = new Closer<>();
* Closer<IOException> closer2 = closer1.split();
* Closer<MyOtherException> closer3 = closer1.split() ) {
* closer2.push( this.someJavaIOCloseable::close );
* closer1.pushAll( this.myListOfCloseables1, MyCloseableTypeThrowingMyException::close );
* closer3.pushAll( this.myListOfCloseables2, MyCloseableTypeThrowingMyOtherException::close );
* }
* }
*
*
* The multiple closers will share the same state, which means the first
* exception to be caught by any of the closers will be the one to be re-thrown,
* and all subsequent exceptions caught by any closer will be added as suppressed
* to this first exception.
* Be careful though, you will have to close every single closer.
* Closing just the original one will not be enough.
*
* @param E The supertype of exceptions this closer can catch and re-throw,
* besides {@link RuntimeException} and {@link Error}.
*
* @author Yoann Rodiere
*/
public final class Closer implements AutoCloseable {
private final State state;
public Closer() {
this( new State() );
}
private Closer(State state) {
this.state = state;
}
/**
* Execute the given close {@code operator} immediately on
* the given {@code objectToClose}, swallowing any throwable in order to
* throw it {@link #close() later}.
* @param operator An operator to close {@code objectToClose}. Accepts lambdas
* such as {@code MyType::close}.
* @param objectToClose An object to close
*/
public void push(ClosingOperator operator, T objectToClose) {
try {
operator.close( objectToClose );
}
catch (Throwable t) {
state.addThrowable( this, t );
}
}
/**
* Close the given {@code closeable} immediately,
* swallowing any throwable in order to throw it {@link #close() later}.
*
* @param closeable A {@link GenericCloseable} to close. Accepts lambdas
* such as {@code myObject::close}.
*/
public void push(GenericCloseable extends E> closeable) {
push( GenericCloseable::close, closeable );
}
/**
* Execute the given close {@code operator} immediately on
* each element of the given iterable, swallowing any throwable in order to
* throw them {@link #close() later}.
* @param operator An operator to close each element in {@code objectsToClose}. Accepts lambdas
* such as {@code MyType::close}.
* @param objectsToClose An iterable of objects to close
*/
public void pushAll(ClosingOperator operator, Iterable objectsToClose) {
for ( T objectToClose : objectsToClose ) {
push( operator, objectToClose );
}
}
/**
* Execute the given close {@code operator} immediately on
* each element of the given array, swallowing any throwable in order to
* throw them {@link #close() later}.
* @param operator An operator to close each element in {@code objectsToClose}. Accepts lambdas
* such as {@code MyType::close}.
* @param objectsToClose An array of objects to close
*/
@SafeVarargs
public final void pushAll(ClosingOperator operator, T ... objectsToClose) {
for ( T objectToClose : objectsToClose ) {
push( operator, objectToClose );
}
}
/**
* @return A closer sharing the same state as {@code this}, allowing to handle
* multiple exception types.
* @see splitting
*/
public Closer split() {
return new Closer<>();
}
/**
* @throws E The first throwable caught when executing the {@code push} methods, if any.
* Any throwable caught after the first will have been
* {@link Throwable#addSuppressed(Throwable) suppressed}.
*/
@Override
public void close() throws E {
state.close( this );
}
/**
* This is implemented in a separate class so that multiple closers
* can share the same state, allowing them to elect a single "first throwable".
*/
private static class State {
private Closer> firstThrower;
private Throwable firstThrowable;
public void addThrowable(Closer> source, Throwable throwable) {
if ( firstThrowable == null ) {
firstThrowable = throwable;
firstThrower = source;
}
else {
firstThrowable.addSuppressed( throwable );
}
}
@SuppressWarnings("unchecked")
public void close(Closer source) throws E {
if ( firstThrowable != null && source == firstThrower ) {
try {
if ( firstThrowable instanceof RuntimeException ) {
throw (RuntimeException) firstThrowable;
}
else if ( firstThrowable instanceof Error ) {
throw (Error) firstThrowable;
}
else if ( firstThrowable != null ) {
/*
* At this point we know that throwable is an instance of E,
* because that's the only checked exception that the source
* can catch.
*/
throw (E) firstThrowable;
}
}
finally {
// Ensure the next calls to Closer.close won't throw
this.firstThrower = null;
this.firstThrowable = null;
}
}
}
}
}