AbstractConcurrentInitializer.java
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.lang3.concurrent;
import java.util.Objects;
import org.apache.commons.lang3.builder.AbstractSupplier;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.commons.lang3.function.FailableConsumer;
import org.apache.commons.lang3.function.FailableSupplier;
/**
* Abstracts and defines operations for {@link ConcurrentInitializer} implementations.
*
* @param <T> the type of the object managed by this initializer class.
* @param <E> The exception type thrown by {@link #initialize()}.
* @since 3.14.0
*/
public abstract class AbstractConcurrentInitializer<T, E extends Exception> implements ConcurrentInitializer<T> {
/**
* Builds a new instance for subclasses.
*
* @param <T> the type of the object managed by the initializer class.
* @param <I> the type of the initializer class.
* @param <B> the type of builder.
* @param <E> The exception type thrown by {@link #initialize()}.
*/
public abstract static class AbstractBuilder<I extends AbstractConcurrentInitializer<T, E>, T, B extends AbstractBuilder<I, T, B, E>, E extends Exception>
extends AbstractSupplier<I, B, E> {
/**
* Closer consumer called by {@link #close()}.
*/
private FailableConsumer<T, ? extends Exception> closer = FailableConsumer.nop();
/**
* Initializer supplier called by {@link #initialize()}.
*/
private FailableSupplier<T, ? extends Exception> initializer = FailableSupplier.nul();
/**
* Constructs a new instance.
*/
public AbstractBuilder() {
// empty
}
/**
* Gets the closer consumer called by {@link #close()}.
*
* @return the closer consumer called by {@link #close()}.
*/
public FailableConsumer<T, ? extends Exception> getCloser() {
return closer;
}
/**
* Gets the initializer supplier called by {@link #initialize()}.
*
* @return the initializer supplier called by {@link #initialize()}.
*/
public FailableSupplier<T, ? extends Exception> getInitializer() {
return initializer;
}
/**
* Sets the closer consumer called by {@link #close()}.
*
* @param closer the consumer called by {@link #close()}.
* @return {@code this} instance.
*/
public B setCloser(final FailableConsumer<T, ? extends Exception> closer) {
this.closer = closer != null ? closer : FailableConsumer.nop();
return asThis();
}
/**
* Sets the initializer supplier called by {@link #initialize()}.
*
* @param initializer the supplier called by {@link #initialize()}.
* @return {@code this} instance.
*/
public B setInitializer(final FailableSupplier<T, ? extends Exception> initializer) {
this.initializer = initializer != null ? initializer : FailableSupplier.nul();
return asThis();
}
}
/**
* Closer consumer called by {@link #close()}.
*/
private final FailableConsumer<? super T, ? extends Exception> closer;
/**
* Initializer supplier called by {@link #initialize()}.
*/
private final FailableSupplier<? extends T, ? extends Exception> initializer;
/**
* Constructs a new instance.
*/
public AbstractConcurrentInitializer() {
this(FailableSupplier.nul(), FailableConsumer.nop());
}
/**
* Constructs a new instance.
*
* @param initializer the initializer supplier called by {@link #initialize()}.
* @param closer the closer consumer called by {@link #close()}.
*/
AbstractConcurrentInitializer(final FailableSupplier<? extends T, ? extends Exception> initializer, final FailableConsumer<? super T, ? extends Exception> closer) {
this.closer = Objects.requireNonNull(closer, "closer");
this.initializer = Objects.requireNonNull(initializer, "initializer");
}
/**
* Calls the closer with the manager object.
*
* @throws ConcurrentException Thrown by the closer.
* @since 3.14.0
*/
public void close() throws ConcurrentException {
if (isInitialized()) {
try {
closer.accept(get());
} catch (final Exception e) {
// This intentionally does not duplicate the logic in initialize
// or care about the generic type E.
//
// initialize may run inside a Future and it does not make sense
// to wrap an exception stored inside a Future. However close()
// always runs on the current thread so it always wraps in a
// ConcurrentException
throw new ConcurrentException(ExceptionUtils.throwUnchecked(e));
}
}
}
/**
* Gets an Exception with a type of E as defined by a concrete subclass of this class.
*
* @param e The actual exception that was thrown
* @return a new exception with the actual type of E, that wraps e.
*/
protected abstract E getTypedException(Exception e);
/**
* Creates and initializes the object managed by this {@code
* ConcurrentInitializer}. This method is called by {@link #get()} when the object is accessed for the first time. An implementation can focus on the
* creation of the object. No synchronization is needed, as this is already handled by {@code get()}.
* <p>
* Subclasses and clients that do not provide an initializer are expected to implement this method.
* </p>
*
* @return the managed data object
* @throws E if an error occurs during object creation
*/
@SuppressWarnings("unchecked")
protected T initialize() throws E {
try {
return initializer.get();
} catch (final Exception e) {
// Do this first so we don't pass a RuntimeException or Error into an exception constructor
ExceptionUtils.throwUnchecked(e);
// Depending on the subclass of AbstractConcurrentInitializer E can be Exception or ConcurrentException
// if E is Exception the if statement below will always be true, and the new Exception object created
// in getTypedException will never be thrown. If E is ConcurrentException and the if statement is false
// we throw the ConcurrentException returned from getTypedException, which wraps the original exception.
final E typedException = getTypedException(e);
if (typedException.getClass().isAssignableFrom(e.getClass())) {
throw (E) e;
}
throw typedException;
}
}
/**
* Returns true if initialization has been completed. If initialization threw an exception this will return false, but it will return true if a subsequent
* call to initialize completes successfully. If the implementation of ConcurrentInitializer can initialize multiple objects, this will only return true if
* all objects have been initialized.
*
* @return true if all initialization is complete, otherwise false
*/
protected abstract boolean isInitialized();
}