Use LazyInitializer without subclassing. (#1123)

* Use LazyInitializer without subclassing.

- Allow a Supplier for initialized object
- Allow a Consumer to close the managed object

* Allow any checked exception in the argument to supplier and consumer

* Make all impls of AbstractConcurrentInitializer concrete with builders and tests

* add tests for closer

* Use ConcurrentException as the wrapper for close

* Changes requested in code review

---------

Co-authored-by: Gary Gregory <gardgregory@gmail.com>
Co-authored-by: Benjamin Confino <benjamic@uk.ibm.com>
This commit is contained in:
Gary Gregory 2023-10-20 13:53:08 -04:00 committed by GitHub
parent 038e016d2a
commit 5d146cd069
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 1577 additions and 93 deletions

View File

@ -0,0 +1,42 @@
/*
* 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.builder;
import org.apache.commons.lang3.function.FailableSupplier;
/**
* Abstracts supplying an instance of {@code T}. Use to implement the builder pattern.
*
* @param <T> the type of instances to build.
* @param <B> the type of builder.
* @param <E> The kind of thrown exception or error.
* @since 3.14.0
*/
public abstract class AbstractSupplier<T, B extends AbstractSupplier<T, B, E>, E extends Throwable> implements FailableSupplier<T, E> {
/**
* Returns this instance typed as the proper subclass type.
*
* @return this instance typed as the proper subclass type.
*/
@SuppressWarnings("unchecked")
protected B asThis() {
return (B) this;
}
}

View File

@ -17,6 +17,13 @@
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 ConcurrentInitializer implementations.
*
@ -26,15 +33,150 @@ package org.apache.commons.lang3.concurrent;
*/
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();
/**
* 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 this
*/
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 this
*/
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));
}
}
}
/**
* 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
*/
protected abstract T initialize() throws E;
@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
@ -45,4 +187,12 @@ public abstract class AbstractConcurrentInitializer<T, E extends Exception> impl
*/
protected abstract boolean isInitialized();
/**
* 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);
}

View File

@ -18,6 +18,9 @@ package org.apache.commons.lang3.concurrent;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.commons.lang3.function.FailableConsumer;
import org.apache.commons.lang3.function.FailableSupplier;
/**
* A specialized implementation of the {@link ConcurrentInitializer} interface
* based on an {@link AtomicReference} variable.
@ -62,13 +65,58 @@ import java.util.concurrent.atomic.AtomicReference;
* @since 3.0
* @param <T> the type of the object managed by this initializer class
*/
public abstract class AtomicInitializer<T> extends AbstractConcurrentInitializer<T, RuntimeException> {
public class AtomicInitializer<T> extends AbstractConcurrentInitializer<T, ConcurrentException> {
/**
* Builds a new instance.
*
* @param <T> the type of the object managed by the initializer.
* @param <I> the type of the initializer managed by this builder.
* @since 3.14.0
*/
public static class Builder<I extends AtomicInitializer<T>, T> extends AbstractBuilder<I, T, Builder<I, T>, ConcurrentException> {
@SuppressWarnings("unchecked")
@Override
public I get() {
return (I) new AtomicInitializer(getInitializer(), getCloser());
}
}
private static final Object NO_INIT = new Object();
/** Holds the reference to the managed object. */
private final AtomicReference<T> reference = new AtomicReference<>(getNoInit());
/**
* Creates a new builder.
*
* @param <T> the type of object to build.
* @return a new builder.
* @since 3.14.0
*/
public static <T> Builder<AtomicInitializer<T>, T> builder() {
return new Builder<>();
}
/**
* Constructs a new instance.
*/
public AtomicInitializer() {
// empty
}
/**
* Constructs a new instance.
*
* @param initializer the initializer supplier called by {@link #initialize()}.
* @param closer the closer consumer called by {@link #close()}.
*/
private AtomicInitializer(final FailableSupplier<T, ConcurrentException> initializer, final FailableConsumer<T, ConcurrentException> closer) {
super(initializer, closer);
}
/**
* Returns the object managed by this initializer. The object is created if
* it is not available yet and stored internally. This method always returns
@ -109,4 +157,12 @@ public abstract class AtomicInitializer<T> extends AbstractConcurrentInitializer
public boolean isInitialized() {
return reference.get() != NO_INIT;
}
/**
* {@inheritDoc}
*/
@Override
protected ConcurrentException getTypedException(Exception e) {
return new ConcurrentException(e);
}
}

View File

@ -18,6 +18,9 @@ package org.apache.commons.lang3.concurrent;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.commons.lang3.function.FailableConsumer;
import org.apache.commons.lang3.function.FailableSupplier;
/**
* A specialized {@link ConcurrentInitializer} implementation which is similar
* to {@link AtomicInitializer}, but ensures that the {@link #initialize()}
@ -51,7 +54,24 @@ import java.util.concurrent.atomic.AtomicReference;
* @since 3.0
* @param <T> the type of the object managed by this initializer class
*/
public abstract class AtomicSafeInitializer<T> extends AbstractConcurrentInitializer<T, RuntimeException> {
public class AtomicSafeInitializer<T> extends AbstractConcurrentInitializer<T, ConcurrentException> {
/**
* Builds a new instance.
*
* @param <T> the type of the object managed by the initializer.
* @param <I> the type of the initializer managed by this builder.
* @since 3.14.0
*/
public static class Builder<I extends AtomicSafeInitializer<T>, T> extends AbstractBuilder<I, T, Builder<I, T>, ConcurrentException> {
@SuppressWarnings("unchecked")
@Override
public I get() {
return (I) new AtomicSafeInitializer(getInitializer(), getCloser());
}
}
private static final Object NO_INIT = new Object();
@ -61,6 +81,34 @@ public abstract class AtomicSafeInitializer<T> extends AbstractConcurrentInitial
/** Holds the reference to the managed object. */
private final AtomicReference<T> reference = new AtomicReference<>(getNoInit());
/**
* Creates a new builder.
*
* @param <T> the type of object to build.
* @return a new builder.
* @since 3.14.0
*/
public static <T> Builder<AtomicSafeInitializer<T>, T> builder() {
return new Builder<>();
}
/**
* Constructs a new instance.
*/
public AtomicSafeInitializer() {
// empty
}
/**
* Constructs a new instance.
*
* @param initializer the initializer supplier called by {@link #initialize()}.
* @param closer the closer consumer called by {@link #close()}.
*/
private AtomicSafeInitializer(final FailableSupplier<T, ConcurrentException> initializer, final FailableConsumer<T, ConcurrentException> closer) {
super(initializer, closer);
}
/**
* Gets (and initialize, if not initialized yet) the required object
*
@ -97,4 +145,12 @@ public abstract class AtomicSafeInitializer<T> extends AbstractConcurrentInitial
public boolean isInitialized() {
return reference.get() != NO_INIT;
}
/**
* {@inheritDoc}
*/
@Override
protected ConcurrentException getTypedException(Exception e) {
return new ConcurrentException(e);
}
}

View File

@ -23,6 +23,9 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import org.apache.commons.lang3.function.FailableConsumer;
import org.apache.commons.lang3.function.FailableSupplier;
/**
* A class that allows complex initialization operations in a background task.
*
@ -82,7 +85,42 @@ import java.util.concurrent.Future;
* @since 3.0
* @param <T> the type of the object managed by this initializer class
*/
public abstract class BackgroundInitializer<T> extends AbstractConcurrentInitializer<T, Exception> {
public class BackgroundInitializer<T> extends AbstractConcurrentInitializer<T, Exception> {
/**
* Builds a new instance.
*
* @param <T> the type of the object managed by the initializer.
* @param <I> the type of the initializer managed by this builder.
* @since 3.14.0
*/
public static class Builder<I extends BackgroundInitializer<T>, T> extends AbstractBuilder<I, T, Builder<I, T>, Exception> {
/**
* The external executor service for executing tasks. null is an permitted value.
*/
private ExecutorService externalExecutor;
/**
* Sets the external executor service for executing tasks. null is an permitted value.
*
* @see org.apache.commons.lang3.concurrent.BackgroundInitializer#setExternalExecutor(ExecutorService)
*
* @param externalExecutor the {@link ExecutorService} to be used.
* @return this
*/
public Builder<I, T> setExternalExecutor(final ExecutorService externalExecutor) {
this.externalExecutor = externalExecutor;
return asThis();
}
@SuppressWarnings("unchecked")
@Override
public I get() {
return (I) new BackgroundInitializer(getInitializer(), getCloser(), externalExecutor);
}
}
/** The external executor service for executing tasks. */
private ExecutorService externalExecutor; // @GuardedBy("this")
@ -115,6 +153,29 @@ public abstract class BackgroundInitializer<T> extends AbstractConcurrentInitial
setExternalExecutor(exec);
}
/**
* Creates a new builder.
*
* @param <T> the type of object to build.
* @return a new builder.
* @since 3.14.0
*/
public static <T> Builder<BackgroundInitializer<T>, T> builder() {
return new Builder<>();
}
/**
* Constructs a new instance.
*
* @param initializer the initializer supplier called by {@link #initialize()}.
* @param closer the closer consumer called by {@link #close()}.
* @param exec the {@link ExecutorService} to be used @see #setExternalExecutor(ExecutorService)
*/
private BackgroundInitializer(final FailableSupplier<T, ConcurrentException> initializer, final FailableConsumer<T, ConcurrentException> closer, final ExecutorService exec) {
super(initializer, closer);
setExternalExecutor(exec);
}
/**
* Returns the external {@link ExecutorService} to be used by this class.
*
@ -341,4 +402,13 @@ public abstract class BackgroundInitializer<T> extends AbstractConcurrentInitial
}
}
}
/**
* {@inheritDoc}
*/
@Override
protected Exception getTypedException(Exception e) {
//This Exception object will be used for type comparison in AbstractConcurrentInitializer.initialize but not thrown
return new Exception(e);
}
}

View File

@ -119,4 +119,13 @@ public class CallableBackgroundInitializer<T> extends BackgroundInitializer<T> {
private void checkCallable(final Callable<T> callable) {
Objects.requireNonNull(callable, "callable");
}
/**
* {@inheritDoc}
*/
@Override
protected Exception getTypedException(Exception e) {
//This Exception object will be used for type comparison in AbstractConcurrentInitializer.initialize but not thrown
return new Exception(e);
}
}

View File

@ -16,29 +16,25 @@
*/
package org.apache.commons.lang3.concurrent;
import org.apache.commons.lang3.function.FailableConsumer;
import org.apache.commons.lang3.function.FailableSupplier;
/**
* This class provides a generic implementation of the lazy initialization
* pattern.
* This class provides a generic implementation of the lazy initialization pattern.
*
* <p>
* Sometimes an application has to deal with an object only under certain
* circumstances, e.g. when the user selects a specific menu item or if a
* special event is received. If the creation of the object is costly or the
* consumption of memory or other system resources is significant, it may make
* sense to defer the creation of this object until it is really needed. This is
* a use case for the lazy initialization pattern.
* Sometimes an application has to deal with an object only under certain circumstances, e.g. when the user selects a specific menu item or if a special event
* is received. If the creation of the object is costly or the consumption of memory or other system resources is significant, it may make sense to defer the
* creation of this object until it is really needed. This is a use case for the lazy initialization pattern.
* </p>
* <p>
* This abstract base class provides an implementation of the double-check idiom
* for an instance field as discussed in Joshua Bloch's "Effective Java", 2nd
* edition, item 71. The class already implements all necessary synchronization.
* A concrete subclass has to implement the {@code initialize()} method, which
* This abstract base class provides an implementation of the double-check idiom for an instance field as discussed in Joshua Bloch's "Effective Java", 2nd
* edition, item 71. The class already implements all necessary synchronization. A concrete subclass has to implement the {@code initialize()} method, which
* actually creates the wrapped data object.
* </p>
* <p>
* As an usage example consider that we have a class {@code ComplexObject} whose
* instantiation is a complex operation. In order to apply lazy initialization
* to this class, a subclass of {@link LazyInitializer} has to be created:
* As an usage example consider that we have a class {@code ComplexObject} whose instantiation is a complex operation. In order to apply lazy initialization to
* this class, a subclass of {@link LazyInitializer} has to be created:
* </p>
*
* <pre>
@ -51,9 +47,8 @@ package org.apache.commons.lang3.concurrent;
* </pre>
*
* <p>
* Access to the data object is provided through the {@code get()} method. So,
* code that wants to obtain the {@code ComplexObject} instance would simply
* look like this:
* Access to the data object is provided through the {@code get()} method. So, code that wants to obtain the {@code ComplexObject} instance would simply look
* like this:
* </p>
*
* <pre>
@ -65,32 +60,75 @@ package org.apache.commons.lang3.concurrent;
* </pre>
*
* <p>
* If multiple threads call the {@code get()} method when the object has not yet
* been created, they are blocked until initialization completes. The algorithm
* guarantees that only a single instance of the wrapped object class is
* created, which is passed to all callers. Once initialized, calls to the
* {@code get()} method are pretty fast because no synchronization is needed
* (only an access to a <b>volatile</b> member field).
* If multiple threads call the {@code get()} method when the object has not yet been created, they are blocked until initialization completes. The algorithm
* guarantees that only a single instance of the wrapped object class is created, which is passed to all callers. Once initialized, calls to the {@code get()}
* method are pretty fast because no synchronization is needed (only an access to a <b>volatile</b> member field).
* </p>
*
* @since 3.0
* @param <T> the type of the object managed by this initializer class
* @param <T> the type of the object managed by the initializer.
*/
public abstract class LazyInitializer<T> extends AbstractConcurrentInitializer<T, ConcurrentException> {
public class LazyInitializer<T> extends AbstractConcurrentInitializer<T, ConcurrentException> {
/**
* Builds a new instance.
*
* @param <T> the type of the object managed by the initializer.
* @param <I> the type of the initializer managed by this builder.
* @since 3.14.0
*/
public static class Builder<I extends LazyInitializer<T>, T> extends AbstractBuilder<I, T, Builder<I, T>, ConcurrentException> {
@SuppressWarnings("unchecked")
@Override
public I get() {
return (I) new LazyInitializer(getInitializer(), getCloser());
}
}
/**
* A unique value indicating an un-initialzed instance.
*/
private static final Object NO_INIT = new Object();
/**
* Creates a new builder.
*
* @param <T> the type of object to build.
* @return a new builder.
* @since 3.14.0
*/
public static <T> Builder<LazyInitializer<T>, T> builder() {
return new Builder<>();
}
/** Stores the managed object. */
@SuppressWarnings("unchecked")
private volatile T object = (T) NO_INIT;
/**
* Returns the object wrapped by this instance. On first access the object
* is created. After that it is cached and can be accessed pretty fast.
* Constructs a new instance.
*/
public LazyInitializer() {
// empty
}
/**
* Constructs a new instance.
*
* @param initializer the initializer supplier called by {@link #initialize()}.
* @param closer the closer consumer called by {@link #close()}.
*/
private LazyInitializer(final FailableSupplier<T, ConcurrentException> initializer, final FailableConsumer<T, ConcurrentException> closer) {
super(initializer, closer);
}
/**
* Returns the object wrapped by this instance. On first access the object is created. After that it is cached and can be accessed pretty fast.
*
* @return the object initialized by this {@link LazyInitializer}
* @throws ConcurrentException if an error occurred during initialization of
* the object
* @throws ConcurrentException if an error occurred during initialization of the object
*/
@Override
public T get() throws ConcurrentException {
@ -121,4 +159,12 @@ public abstract class LazyInitializer<T> extends AbstractConcurrentInitializer<T
return object != NO_INIT;
}
/**
* {@inheritDoc}
*/
@Override
protected ConcurrentException getTypedException(Exception e) {
return new ConcurrentException(e);
}
}

View File

@ -215,6 +215,39 @@ public class MultiBackgroundInitializer
return childInitializers.values().stream().allMatch(BackgroundInitializer::isInitialized);
}
/**
* Calls the closer of all child {@code BackgroundInitializer} objects
*
* @throws ConcurrentException throws an ConcurrentException that will have all other exceptions as suppressed exceptions. ConcurrentException thrown by children will be unwrapped.
* @since 3.14.0
*/
@Override
public void close() throws ConcurrentException {
ConcurrentException exception = null;
for (BackgroundInitializer<?> child : childInitializers.values()) {
try {
child.close();
} catch (Exception e) {
if (exception == null) {
exception = new ConcurrentException();
}
if (e instanceof ConcurrentException) {
// Because ConcurrentException is only created by classes in this package
// we can safely unwrap it.
exception.addSuppressed(e.getCause());
} else {
exception.addSuppressed(e);
}
}
}
if (exception != null) {
throw exception;
}
}
/**
* A data class for storing the results of the background initialization
* performed by {@link MultiBackgroundInitializer}. Objects of this inner

View File

@ -0,0 +1,184 @@
/*
* 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 static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import java.io.IOException;
import java.sql.SQLException;
import org.apache.commons.lang3.function.FailableConsumer;
import org.apache.commons.lang3.function.FailableSupplier;
import org.junit.jupiter.api.Test;
/**
* An abstract base class for tests of exceptions thrown during initialize and close methods
* on concrete {@code ConcurrentInitializer} implementations.
*
* This class provides some basic tests for initializer implementations. Derived
* class have to create a {@link ConcurrentInitializer} object on which the
* tests are executed.
*/
public abstract class AbstractConcurrentInitializerCloseAndExceptionsTest extends AbstractConcurrentInitializerTest {
/**
* This method tests that if a AbstractConcurrentInitializer.initialize method catches a
* ConcurrentException it will rethrow it without wrapping it.
*/
@Test
public void testSupplierThrowsConcurrentException() {
final ConcurrentException concurrentException = new ConcurrentException();
@SuppressWarnings("unchecked")
final ConcurrentInitializer<CloseableObject> initializer = createInitializerThatThrowsException(
() -> {
if ("test".equals("test")) {
throw concurrentException;
}
return new CloseableObject();
},
FailableConsumer.NOP);
try {
initializer.get();
fail();
} catch (ConcurrentException e) {
assertEquals(concurrentException, e);
}
}
/**
* This method tests that if AbstractConcurrentInitializer.initialize catches a checked
* exception it will rethrow it wrapped in a ConcurrentException
*/
@SuppressWarnings("unchecked") //for NOP
@Test
public void testSupplierThrowsCheckedException() {
final ConcurrentInitializer<CloseableObject> initializer = createInitializerThatThrowsException(
() -> methodThatThrowsException(ExceptionToThrow.IOException),
FailableConsumer.NOP);
assertThrows(ConcurrentException.class, () -> initializer.get());
}
/**
* This method tests that if AbstractConcurrentInitializer.initialize catches a runtime exception
* it will not be wrapped in a ConcurrentException
*/
@SuppressWarnings("unchecked")
@Test
public void testSupplierThrowsRuntimeException() {
final ConcurrentInitializer<CloseableObject> initializer = createInitializerThatThrowsException(
() -> methodThatThrowsException(ExceptionToThrow.NullPointerException),
FailableConsumer.NOP);
assertThrows(NullPointerException.class, () -> initializer.get());
}
/**
* This method tests that if AbstractConcurrentInitializer.close catches a
* ConcurrentException it will rethrow it wrapped in a ConcurrentException
*/
@SuppressWarnings("rawtypes")
@Test
public void testCloserThrowsCheckedException() throws ConcurrentException {
final ConcurrentInitializer<CloseableObject> initializer = createInitializerThatThrowsException(
CloseableObject::new,
(CloseableObject) -> methodThatThrowsException(ExceptionToThrow.IOException));
try {
initializer.get();
((AbstractConcurrentInitializer) initializer).close();
fail();
} catch (Exception e) {
assertThat(e, instanceOf(ConcurrentException.class));
assertThat(e.getCause(), instanceOf(IOException.class));
}
}
/**
* This method tests that if AbstractConcurrentInitializer.close catches a
* RuntimeException it will throw it withuot wrapping it in a ConcurrentException
*/
@SuppressWarnings("rawtypes")
@Test
public void testCloserThrowsRuntimeException() throws ConcurrentException {
final ConcurrentInitializer<CloseableObject> initializer = createInitializerThatThrowsException(
CloseableObject::new,
(CloseableObject) -> methodThatThrowsException(ExceptionToThrow.NullPointerException));
initializer.get();
assertThrows(NullPointerException.class, () -> {
((AbstractConcurrentInitializer) initializer).close();
});
}
/**
* This method tests that if AbstractConcurrentInitializer.close actually closes the wrapped object
*/
@SuppressWarnings("rawtypes")
@Test
public void testWorkingCloser() throws Exception {
final ConcurrentInitializer<CloseableObject> initializer = createInitializerThatThrowsException(
CloseableObject::new,
CloseableObject::close);
CloseableObject cloesableObject = initializer.get();
assertFalse(cloesableObject.isClosed());
((AbstractConcurrentInitializer) initializer).close();
assertTrue(cloesableObject.isClosed());
}
protected enum ExceptionToThrow {
IOException,
SQLException,
NullPointerException
}
// The use of enums rather than accepting an Exception as the input means we can have
// multiple exception types on the method signature.
protected static CloseableObject methodThatThrowsException(ExceptionToThrow input) throws IOException, SQLException, ConcurrentException {
switch (input) {
case IOException:
throw new IOException();
case SQLException:
throw new SQLException();
case NullPointerException:
throw new NullPointerException();
default:
fail();
return new CloseableObject();
}
}
protected abstract ConcurrentInitializer<CloseableObject> createInitializerThatThrowsException(
FailableSupplier<CloseableObject, ? extends Exception> supplier, FailableConsumer<CloseableObject, ? extends Exception> closer);
protected static final class CloseableObject {
boolean closed;
public boolean isClosed() {
return closed;
}
public void close() {
closed = true;
}
}
}

View File

@ -0,0 +1,43 @@
/*
* 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 org.apache.commons.lang3.function.FailableConsumer;
import org.apache.commons.lang3.function.FailableSupplier;
/**
* Test class for {@code AtomicInitializer}.
*/
public class AtomicInitializerSupplierTest extends AbstractConcurrentInitializerCloseAndExceptionsTest {
/**
* Returns the initializer to be tested.
*
* @return the {@code AtomicInitializer}
*/
@Override
protected ConcurrentInitializer<Object> createInitializer() {
return AtomicInitializer.<Object>builder().setInitializer(() -> new Object()).get();
}
@Override
protected ConcurrentInitializer<CloseableObject> createInitializerThatThrowsException(
final FailableSupplier<CloseableObject, ? extends Exception> supplier,
final FailableConsumer<CloseableObject, ? extends Exception> closer) {
return AtomicInitializer.<CloseableObject>builder().setInitializer(supplier).setCloser(closer).get();
}
}

View File

@ -0,0 +1,97 @@
/*
* 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 static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.commons.lang3.concurrent.AbstractConcurrentInitializerCloseAndExceptionsTest.CloseableObject;
import org.apache.commons.lang3.function.FailableConsumer;
import org.apache.commons.lang3.function.FailableSupplier;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/**
* Test class for {@code AtomicSafeInitializer} which also serves as a simple example.
*/
public class AtomicSafeInitializerSupplierTest extends AbstractConcurrentInitializerCloseAndExceptionsTest {
/** An initCounter used in testing. Reset before each test */
private AtomicInteger initCounter = new AtomicInteger();
/** A supplier method used in testing */
private Object incAndMakeObject() {
initCounter.incrementAndGet();
return new Object();
}
@Override
protected ConcurrentInitializer<CloseableObject> createInitializerThatThrowsException(
final FailableSupplier<CloseableObject, ? extends Exception> supplier,
final FailableConsumer<CloseableObject, ? extends Exception> closer) {
return AtomicSafeInitializer.<CloseableObject>builder().setInitializer(supplier).setCloser(closer).get();
}
@BeforeEach
public void setUp() {
initCounter = new AtomicInteger();
}
/**
* Creates the initializer to be tested.
*
* @return the {@code AtomicSafeInitializer} under test
*/
@Override
protected ConcurrentInitializer<Object> createInitializer() {
return AtomicSafeInitializer.<Object>builder().setInitializer(this::incAndMakeObject).get();
}
/**
* Tests that initialize() is called only once.
*
* @throws org.apache.commons.lang3.concurrent.ConcurrentException because {@link #testGetConcurrent()} may throw it
* @throws InterruptedException because {@link #testGetConcurrent()} may throw it
*/
@Test
public void testNumberOfInitializeInvocations() throws ConcurrentException, InterruptedException {
testGetConcurrent();
assertEquals(1, initCounter.get(), "Wrong number of invocations");
}
@Test
public void testGetThatReturnsNullFirstTime() throws ConcurrentException {
final AtomicSafeInitializer<Object> initializer = new AtomicSafeInitializer<Object>() {
final AtomicBoolean firstRun = new AtomicBoolean(true);
@Override
protected Object initialize() {
if (firstRun.getAndSet(false)) {
return null;
} else {
return new Object();
}
}
};
assertNull(initializer.get());
assertNull(initializer.get());
}
}

View File

@ -0,0 +1,148 @@
/*
* 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 static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.concurrent.ExecutorService;
import org.apache.commons.lang3.function.FailableConsumer;
import org.apache.commons.lang3.function.FailableSupplier;
import org.junit.jupiter.api.Test;
public class BackgroundInitializerSupplierTest extends BackgroundInitializerTest {
/**
* Tests that close() method closes the wrapped object
*
* @throws Exception
*/
@Test
public void testClose() throws Exception {
final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
assertFalse(init.getCloseableCounter().isClosed(), "closed without close() call");
init.close();
assertFalse(init.getCloseableCounter().isClosed(), "closed() succeeded before start()");
init.start();
init.get(); //ensure the Future has completed.
assertFalse(init.getCloseableCounter().isClosed(), "closed() succeeded after start() but before close()");
init.close();
assertTrue(init.getCloseableCounter().isClosed(), "closed() did not succeed");
}
/**
* Tests that close() wraps a checked exception in a ConcurrentException
*
* @throws Exception
*/
@Test
public void testCloseWithCheckedException() throws Exception {
final IOException ioException = new IOException();
final FailableConsumer<?, ?> IOExceptionConsumer = (CloseableCounter cc) -> {
throw ioException;
};
final AbstractBackgroundInitializerTestImpl init = new SupplierBackgroundInitializerTestImpl(IOExceptionConsumer);
init.start();
init.get(); //ensure the Future has completed.
try {
init.close();
fail();
} catch (Exception e) {
assertThat(e, instanceOf(ConcurrentException.class));
assertSame(ioException, e.getCause());
}
}
/**
* Tests that close() throws a runtime exception
*
* @throws Exception
*/
@Test
public void testCloseWithRuntimeException() throws Exception {
final NullPointerException npe = new NullPointerException();
final FailableConsumer<?, ?> NullPointerExceptionConsumer = (CloseableCounter cc) -> {
throw npe;
};
final AbstractBackgroundInitializerTestImpl init = new SupplierBackgroundInitializerTestImpl(NullPointerExceptionConsumer);
init.start();
init.get(); //ensure the Future has completed.
try {
init.close();
fail();
} catch (Exception e) {
assertSame(npe, e);
}
}
/**
* A concrete implementation of BackgroundInitializer. It is designed as a warpper so the test can
* use the same builder pattern that real code will.
*/
protected static final class SupplierBackgroundInitializerTestImpl extends AbstractBackgroundInitializerTestImpl {
SupplierBackgroundInitializerTestImpl() {
super();
setSupplierAndCloser((CloseableCounter cc) -> cc.close());
}
SupplierBackgroundInitializerTestImpl(FailableConsumer<?, ?> consumer) {
super();
setSupplierAndCloser(consumer);
}
SupplierBackgroundInitializerTestImpl(final ExecutorService exec) {
super(exec);
setSupplierAndCloser((CloseableCounter cc) -> cc.close());
}
private void setSupplierAndCloser(FailableConsumer<?, ?> consumer) {
try {
// Use reflection here because the constructors we need are private
FailableSupplier<?, ?> supplier = () -> initializeInternal();
Field initializer = AbstractConcurrentInitializer.class.getDeclaredField("initializer");
initializer.setAccessible(true);
initializer.set(this, supplier);
Field closer = AbstractConcurrentInitializer.class.getDeclaredField("closer");
closer.setAccessible(true);
closer.set(this, consumer);
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
fail();
}
}
}
protected AbstractBackgroundInitializerTestImpl getBackgroundInitializerTestImpl() {
return new SupplierBackgroundInitializerTestImpl();
}
protected SupplierBackgroundInitializerTestImpl getBackgroundInitializerTestImpl(final ExecutorService exec) {
return new SupplierBackgroundInitializerTestImpl(exec);
}
}

View File

@ -29,6 +29,8 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.commons.lang3.AbstractLangTest;
@ -42,10 +44,10 @@ public class BackgroundInitializerTest extends AbstractLangTest {
*
* @param init the initializer to test
*/
private void checkInitialize(final BackgroundInitializerTestImpl init) throws ConcurrentException {
final Integer result = init.get();
private void checkInitialize(final AbstractBackgroundInitializerTestImpl init) throws ConcurrentException {
final Integer result = init.get().getInitializeCalls();
assertEquals(1, result.intValue(), "Wrong result");
assertEquals(1, init.initializeCalls, "Wrong number of invocations");
assertEquals(1, init.getCloseableCounter().getInitializeCalls(), "Wrong number of invocations");
assertNotNull(init.getFuture(), "No future");
}
@ -54,7 +56,7 @@ public class BackgroundInitializerTest extends AbstractLangTest {
*/
@Test
public void testInitialize() throws ConcurrentException {
final BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl();
final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
init.start();
checkInitialize(init);
}
@ -65,7 +67,7 @@ public class BackgroundInitializerTest extends AbstractLangTest {
*/
@Test
public void testGetActiveExecutorBeforeStart() {
final BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl();
final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
assertNull(init.getActiveExecutor(), "Got an executor");
}
@ -76,7 +78,7 @@ public class BackgroundInitializerTest extends AbstractLangTest {
public void testGetActiveExecutorExternal() throws InterruptedException, ConcurrentException {
final ExecutorService exec = Executors.newSingleThreadExecutor();
try {
final BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl(
final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl(
exec);
init.start();
assertSame(exec, init.getActiveExecutor(), "Wrong executor");
@ -92,7 +94,7 @@ public class BackgroundInitializerTest extends AbstractLangTest {
*/
@Test
public void testGetActiveExecutorTemp() throws ConcurrentException {
final BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl();
final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
init.start();
assertNotNull(init.getActiveExecutor(), "No active executor");
checkInitialize(init);
@ -104,7 +106,7 @@ public class BackgroundInitializerTest extends AbstractLangTest {
*/
@Test
public void testInitializeTempExecutor() throws ConcurrentException {
final BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl();
final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
assertTrue(init.start(), "Wrong result of start()");
checkInitialize(init);
assertTrue(init.getActiveExecutor().isShutdown(), "Executor not shutdown");
@ -118,7 +120,7 @@ public class BackgroundInitializerTest extends AbstractLangTest {
public void testSetExternalExecutor() throws ConcurrentException {
final ExecutorService exec = Executors.newCachedThreadPool();
try {
final BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl();
final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
init.setExternalExecutor(exec);
assertEquals(exec, init.getExternalExecutor(), "Wrong executor service");
assertTrue(init.start(), "Wrong result of start()");
@ -137,7 +139,7 @@ public class BackgroundInitializerTest extends AbstractLangTest {
*/
@Test
public void testSetExternalExecutorAfterStart() throws ConcurrentException, InterruptedException {
final BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl();
final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
init.start();
final ExecutorService exec = Executors.newSingleThreadExecutor();
try {
@ -155,7 +157,7 @@ public class BackgroundInitializerTest extends AbstractLangTest {
*/
@Test
public void testStartMultipleTimes() throws ConcurrentException {
final BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl();
final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
assertTrue(init.start(), "Wrong result for start()");
for (int i = 0; i < 10; i++) {
assertFalse(init.start(), "Could start again");
@ -168,7 +170,7 @@ public class BackgroundInitializerTest extends AbstractLangTest {
*/
@Test
public void testGetBeforeStart() {
final BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl();
final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
assertThrows(IllegalStateException.class, init::get);
}
@ -178,7 +180,7 @@ public class BackgroundInitializerTest extends AbstractLangTest {
*/
@Test
public void testGetRuntimeException() {
final BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl();
final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
final RuntimeException rex = new RuntimeException();
init.ex = rex;
init.start();
@ -192,7 +194,7 @@ public class BackgroundInitializerTest extends AbstractLangTest {
*/
@Test
public void testGetCheckedException() {
final BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl();
final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
final Exception ex = new Exception();
init.ex = ex;
init.start();
@ -208,7 +210,7 @@ public class BackgroundInitializerTest extends AbstractLangTest {
@Test
public void testGetInterruptedException() throws InterruptedException {
final ExecutorService exec = Executors.newSingleThreadExecutor();
final BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl(
final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl(
exec);
final CountDownLatch latch1 = new CountDownLatch(1);
init.shouldSleep = true;
@ -242,7 +244,7 @@ public class BackgroundInitializerTest extends AbstractLangTest {
*/
@Test
public void testIsStartedFalse() {
final BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl();
final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
assertFalse(init.isStarted(), "Already started");
}
@ -251,7 +253,7 @@ public class BackgroundInitializerTest extends AbstractLangTest {
*/
@Test
public void testIsStartedTrue() {
final BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl();
final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
init.start();
assertTrue(init.isStarted(), "Not started");
}
@ -261,7 +263,7 @@ public class BackgroundInitializerTest extends AbstractLangTest {
*/
@Test
public void testIsStartedAfterGet() throws ConcurrentException {
final BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl();
final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
init.start();
checkInitialize(init);
assertTrue(init.isStarted(), "Not started");
@ -272,7 +274,7 @@ public class BackgroundInitializerTest extends AbstractLangTest {
*/
@Test
public void testIsInitialized() throws ConcurrentException {
final BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl();
final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
init.enableLatch();
init.start();
assertTrue(init.isStarted(), "Not started"); //Started and Initialized should return opposite values
@ -282,29 +284,37 @@ public class BackgroundInitializerTest extends AbstractLangTest {
assertTrue(init.isInitialized(), "Not initalized after releasing latch");
}
protected AbstractBackgroundInitializerTestImpl getBackgroundInitializerTestImpl() {
return new MethodBackgroundInitializerTestImpl();
}
protected AbstractBackgroundInitializerTestImpl getBackgroundInitializerTestImpl(final ExecutorService exec) {
return new MethodBackgroundInitializerTestImpl(exec);
}
/**
* A concrete implementation of BackgroundInitializer. It also overloads
* some methods that simplify testing.
*/
private static final class BackgroundInitializerTestImpl extends
BackgroundInitializer<Integer> {
protected static class AbstractBackgroundInitializerTestImpl extends
BackgroundInitializer<CloseableCounter> {
/** An exception to be thrown by initialize(). */
Exception ex;
/** A flag whether the background task should sleep a while. */
boolean shouldSleep;
/** The number of invocations of initialize(). */
volatile int initializeCalls;
/** A latch tests can use to control when initialize completes. */
final CountDownLatch latch = new CountDownLatch(1);
boolean waitForLatch = false;
BackgroundInitializerTestImpl() {
/** An object containing the state we are testing */
CloseableCounter counter = new CloseableCounter();
AbstractBackgroundInitializerTestImpl() {
}
BackgroundInitializerTestImpl(final ExecutorService exec) {
AbstractBackgroundInitializerTestImpl(final ExecutorService exec) {
super(exec);
}
@ -316,14 +326,17 @@ public class BackgroundInitializerTest extends AbstractLangTest {
latch.countDown();
}
public CloseableCounter getCloseableCounter() {
return counter;
}
/**
* Records this invocation. Optionally throws an exception or sleeps a
* while.
*
* @throws Exception in case of an error
*/
@Override
protected Integer initialize() throws Exception {
protected CloseableCounter initializeInternal() throws Exception {
if (ex != null) {
throw ex;
}
@ -333,7 +346,47 @@ public class BackgroundInitializerTest extends AbstractLangTest {
if (waitForLatch) {
latch.await();
}
return Integer.valueOf(++initializeCalls);
return counter.increment();
}
}
protected static class MethodBackgroundInitializerTestImpl extends AbstractBackgroundInitializerTestImpl {
MethodBackgroundInitializerTestImpl() {
}
MethodBackgroundInitializerTestImpl(final ExecutorService exec) {
super(exec);
}
@Override
protected CloseableCounter initialize() throws Exception {
return initializeInternal();
}
}
protected static class CloseableCounter {
/** The number of invocations of initialize(). */
AtomicInteger initializeCalls = new AtomicInteger();
/** Has the close consumer successfully reached this object. */
AtomicBoolean closed = new AtomicBoolean();
public CloseableCounter increment() {
initializeCalls.incrementAndGet();
return this;
}
public int getInitializeCalls() {
return initializeCalls.get();
}
public void close() {
closed.set(true);
}
public boolean isClosed() {
return closed.get();
}
}
}

View File

@ -0,0 +1,54 @@
/*
* 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 static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.jupiter.api.Test;
/**
* Tests {@code LazyInitializer}.
*/
public class LazyInitializerCloserTest extends AbstractConcurrentInitializerTest {
private final AtomicBoolean closed = new AtomicBoolean();
/**
* Creates the initializer to be tested. This implementation returns the {@code LazyInitializer} created in the {@code setUp()} method.
*
* @return the initializer to be tested
*/
@Override
protected LazyInitializer<Object> createInitializer() {
return LazyInitializer.builder().setInitializer(Object::new).setCloser(e -> closed.set(true)).get();
}
@Test
public void testIsInitialized() throws ConcurrentException {
final LazyInitializer<Object> initializer = createInitializer();
assertFalse(initializer.isInitialized());
initializer.get();
assertTrue(initializer.isInitialized());
assertFalse(closed.get());
initializer.close();
assertTrue(closed.get());
}
}

View File

@ -0,0 +1,72 @@
/*
* 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 static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.jupiter.api.Test;
/**
* Tests {@code LazyInitializer}.
*/
public class LazyInitializerFailableCloserTest extends AbstractConcurrentInitializerTest {
private final AtomicBoolean closed = new AtomicBoolean();
/**
* Creates the initializer to be tested. This implementation returns the {@code LazyInitializer} created in the {@code setUp()} method.
*
* @return the initializer to be tested
*/
@Override
protected LazyInitializer<Object> createInitializer() {
return LazyInitializer.builder().setInitializer(this::makeObject).setCloser(e -> throwingCloser()).get();
}
private Object makeObject() throws ConcurrentException {
if (closed.get()) {
// Doesn't actually throw, just for the method sig without unused warning.
throw new ConcurrentException("test", new IOException());
}
return new Object();
}
@Test
public void testIsInitialized() throws ConcurrentException {
final LazyInitializer<Object> initializer = createInitializer();
assertFalse(initializer.isInitialized());
initializer.get();
assertTrue(initializer.isInitialized());
assertFalse(closed.get());
initializer.close();
assertTrue(closed.get());
}
private void throwingCloser() throws ConcurrentException {
closed.set(true);
// always false:
if (!closed.get()) {
// Doesn't actually throw, just for the method sig without unused warning.
throw new ConcurrentException("test", new IOException());
}
}
}

View File

@ -0,0 +1,43 @@
/*
* 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 org.apache.commons.lang3.function.FailableConsumer;
import org.apache.commons.lang3.function.FailableSupplier;
/**
* Tests {@code LazyInitializer}.
*/
public class LazyInitializerSupplierTest extends AbstractConcurrentInitializerCloseAndExceptionsTest {
/**
* Creates the initializer to be tested.
*
* @return the initializer to be tested
*/
@Override
protected ConcurrentInitializer<Object> createInitializer() {
return LazyInitializer.<Object>builder().setInitializer(() -> new Object()).get();
}
@Override
protected ConcurrentInitializer<CloseableObject> createInitializerThatThrowsException(
final FailableSupplier<CloseableObject, ? extends Exception> supplier,
final FailableConsumer<CloseableObject, ? extends Exception> closer) {
return LazyInitializer.<CloseableObject>builder().setInitializer(supplier).setCloser(closer).get();
}
}

View File

@ -0,0 +1,261 @@
/*
* 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 static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import java.io.IOException;
import java.lang.reflect.Field;
import org.apache.commons.lang3.function.FailableConsumer;
import org.apache.commons.lang3.function.FailableSupplier;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/**
* Test class for {@link MultiBackgroundInitializer}.
*/
public class MultiBackgroundInitializerSupplierTest extends MultiBackgroundInitializerTest {
private NullPointerException npe;
private IOException ioException;
private FailableConsumer<?, ?> ioExceptionConsumer;
private FailableConsumer<?, ?> nullPointerExceptionConsumer;
@BeforeEach
public void setUpException() throws Exception {
npe = new NullPointerException();
ioException = new IOException();
ioExceptionConsumer = (CloseableCounter cc) -> {
throw ioException;
};
nullPointerExceptionConsumer = (CloseableCounter cc) -> {
throw npe;
};
}
/**
* {@inheritDoc}
*/
@Override
protected AbstractChildBackgroundInitializer createChildBackgroundInitializer() {
return new SupplierChildBackgroundInitializer();
}
/**
* Tests that close() method closes the wrapped object
*
* @throws Exception
*/
@Test
public void testClose()
throws ConcurrentException, InterruptedException {
final AbstractChildBackgroundInitializer childOne = createChildBackgroundInitializer();
final AbstractChildBackgroundInitializer childTwo = createChildBackgroundInitializer();
assertFalse(initializer.isInitialized(), "Initalized without having anything to initalize");
initializer.addInitializer("child one", childOne);
initializer.addInitializer("child two", childTwo);
assertFalse(childOne.getCloseableCounter().isClosed(), "child one closed() succeeded before start()");
assertFalse(childTwo.getCloseableCounter().isClosed(), "child two closed() succeeded before start()");
initializer.start();
long startTime = System.currentTimeMillis();
long waitTime = 3000;
long endTime = startTime + waitTime;
//wait for the children to start
while (!childOne.isStarted() || !childTwo.isStarted()) {
if (System.currentTimeMillis() > endTime) {
fail("children never started");
Thread.sleep(PERIOD_MILLIS);
}
}
assertFalse(childOne.getCloseableCounter().isClosed(), "child one close() succeeded after start() but before close()");
assertFalse(childTwo.getCloseableCounter().isClosed(), "child two close() succeeded after start() but before close()");
childOne.get(); // ensure this child finishes initializing
childTwo.get(); // ensure this child finishes initializing
assertFalse(childOne.getCloseableCounter().isClosed(), "child one initializing succeeded after start() but before close()");
assertFalse(childTwo.getCloseableCounter().isClosed(), "child two initializing succeeded after start() but before close()");
try {
initializer.close();
} catch (Exception e) {
fail();
}
assertTrue(childOne.getCloseableCounter().isClosed(), "child one close() did not succeed");
assertTrue(childOne.getCloseableCounter().isClosed(), "child two close() did not succeed");
}
/**
* Tests that close() wraps a checked exception from a child initializer in an ConcurrentException as the first suppressed under in an ConcurrentException
*
* @throws Exception
*/
@Test
public void testCloseWithCheckedException() throws Exception {
final AbstractChildBackgroundInitializer childOne = new SupplierChildBackgroundInitializer(ioExceptionConsumer);
initializer.addInitializer("child one", childOne);
initializer.start();
long startTime = System.currentTimeMillis();
long waitTime = 3000;
long endTime = startTime + waitTime;
//wait for the children to start
while (! childOne.isStarted()) {
if (System.currentTimeMillis() > endTime) {
fail("children never started");
Thread.sleep(PERIOD_MILLIS);
}
}
childOne.get(); // ensure the Future has completed.
try {
initializer.close();
fail();
} catch (Exception e) {
assertThat(e, instanceOf(ConcurrentException.class));
assertSame(ioException, e.getSuppressed()[0]);
}
}
/**
* Tests that close() wraps a runtime exception from a child initializer as the first suppressed under in an ConcurrentException
*
* @throws Exception
*/
@Test
public void testCloseWithRuntimeException() throws Exception {
final AbstractChildBackgroundInitializer childOne = new SupplierChildBackgroundInitializer(nullPointerExceptionConsumer);
initializer.addInitializer("child one", childOne);
initializer.start();
long startTime = System.currentTimeMillis();
long waitTime = 3000;
long endTime = startTime + waitTime;
//wait for the children to start
while (! childOne.isStarted()) {
if (System.currentTimeMillis() > endTime) {
fail("children never started");
Thread.sleep(PERIOD_MILLIS);
}
}
childOne.get(); // ensure the Future has completed.
try {
initializer.close();
fail();
} catch (Exception e) {
assertThat(e, instanceOf(ConcurrentException.class));
assertSame(npe, e.getSuppressed()[0]);
}
}
/**
* Tests that calling close() on a MultiBackgroundInitializer with two children that both throw exceptions throws
* an ConcurrentException and both the child exceptions are present
*
* @throws Exception
*/
@Test
public void testCloseWithTwoExceptions()
throws ConcurrentException, InterruptedException {
final AbstractChildBackgroundInitializer childOne = new SupplierChildBackgroundInitializer(ioExceptionConsumer);
final AbstractChildBackgroundInitializer childTwo = new SupplierChildBackgroundInitializer(nullPointerExceptionConsumer);
initializer.addInitializer("child one", childOne);
initializer.addInitializer("child two", childTwo);
initializer.start();
final long startTime = System.currentTimeMillis();
final long waitTime = 3000;
final long endTime = startTime + waitTime;
//wait for the children to start
while (! childOne.isStarted() || ! childTwo.isStarted()) {
if (System.currentTimeMillis() > endTime) {
fail("children never started");
Thread.sleep(PERIOD_MILLIS);
}
}
childOne.get(); // ensure this child finishes initializing
childTwo.get(); // ensure this child finishes initializing
try {
initializer.close();
fail();
} catch (Exception e) {
// We don't actually know which order the children will be closed in
boolean foundChildOneException = false;
boolean foundChildTwoException = false;
for (Throwable t : e.getSuppressed()) {
if (t.equals(ioException)) {
foundChildOneException = true;
}
if (t.equals(npe)) {
foundChildTwoException = true;
}
}
assertTrue(foundChildOneException);
assertTrue(foundChildTwoException);
}
}
/**
* A concrete implementation of {@code BackgroundInitializer} used for
* defining background tasks for {@code MultiBackgroundInitializer}.
*/
private static final class SupplierChildBackgroundInitializer extends AbstractChildBackgroundInitializer {
SupplierChildBackgroundInitializer() {
this((CloseableCounter cc) -> cc.close());
}
SupplierChildBackgroundInitializer(FailableConsumer<?, ?> consumer) {
try {
// Use reflection here because the constructors we need are private
final FailableSupplier<?, ?> supplier = () -> initializeInternal();
final Field initializer = AbstractConcurrentInitializer.class.getDeclaredField("initializer");
initializer.setAccessible(true);
initializer.set(this, supplier);
final Field closer = AbstractConcurrentInitializer.class.getDeclaredField("closer");
closer.setAccessible(true);
closer.set(this, consumer);
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
fail();
}
}
}
}

View File

@ -42,10 +42,10 @@ public class MultiBackgroundInitializerTest extends AbstractLangTest {
private static final String CHILD_INIT = "childInitializer";
/** The initializer to be tested. */
private MultiBackgroundInitializer initializer;
protected MultiBackgroundInitializer initializer;
/** A short time to wait for background threads to run. */
private static final long PERIOD_MILLIS = 50;
protected static final long PERIOD_MILLIS = 50;
@BeforeEach
public void setUp() {
@ -63,8 +63,8 @@ public class MultiBackgroundInitializerTest extends AbstractLangTest {
*/
private void checkChild(final BackgroundInitializer<?> child,
final ExecutorService expExec) throws ConcurrentException {
final ChildBackgroundInitializer cinit = (ChildBackgroundInitializer) child;
final Integer result = cinit.get();
final AbstractChildBackgroundInitializer cinit = (AbstractChildBackgroundInitializer) child;
final Integer result = cinit.get().getInitializeCalls();
assertEquals(1, result.intValue(), "Wrong result");
assertEquals(1, cinit.initializeCalls, "Wrong number of executions");
if (expExec != null) {
@ -78,7 +78,7 @@ public class MultiBackgroundInitializerTest extends AbstractLangTest {
*/
@Test
public void testAddInitializerNullName() {
assertThrows(NullPointerException.class, () -> initializer.addInitializer(null, new ChildBackgroundInitializer()));
assertThrows(NullPointerException.class, () -> initializer.addInitializer(null, createChildBackgroundInitializer()));
}
/**
@ -117,7 +117,7 @@ public class MultiBackgroundInitializerTest extends AbstractLangTest {
final int count = 5;
for (int i = 0; i < count; i++) {
initializer.addInitializer(CHILD_INIT + i,
new ChildBackgroundInitializer());
createChildBackgroundInitializer());
}
initializer.start();
final MultiBackgroundInitializer.MultiBackgroundInitializerResults res = initializer
@ -126,7 +126,7 @@ public class MultiBackgroundInitializerTest extends AbstractLangTest {
for (int i = 0; i < count; i++) {
final String key = CHILD_INIT + i;
assertTrue(res.initializerNames().contains(key), "Name not found: " + key);
assertEquals(Integer.valueOf(1), res.getResultObject(key), "Wrong result object");
assertEquals(CloseableCounter.wrapInteger(1), res.getResultObject(key), "Wrong result object");
assertFalse(res.isException(key), "Exception flag");
assertNull(res.getException(key), "Got an exception");
checkChild(res.getInitializer(key), initializer.getActiveExecutor());
@ -175,8 +175,8 @@ public class MultiBackgroundInitializerTest extends AbstractLangTest {
final String initExec = "childInitializerWithExecutor";
final ExecutorService exec = Executors.newSingleThreadExecutor();
try {
final ChildBackgroundInitializer c1 = new ChildBackgroundInitializer();
final ChildBackgroundInitializer c2 = new ChildBackgroundInitializer();
final AbstractChildBackgroundInitializer c1 = createChildBackgroundInitializer();
final AbstractChildBackgroundInitializer c2 = createChildBackgroundInitializer();
c2.setExternalExecutor(exec);
initializer.addInitializer(CHILD_INIT, c1);
initializer.addInitializer(initExec, c2);
@ -201,7 +201,7 @@ public class MultiBackgroundInitializerTest extends AbstractLangTest {
initializer.start();
assertThrows(
IllegalStateException.class,
() -> initializer.addInitializer(CHILD_INIT, new ChildBackgroundInitializer()),
() -> initializer.addInitializer(CHILD_INIT, createChildBackgroundInitializer()),
"Could add initializer after start()!");
initializer.get();
}
@ -275,7 +275,7 @@ public class MultiBackgroundInitializerTest extends AbstractLangTest {
*/
@Test
public void testInitializeRuntimeEx() {
final ChildBackgroundInitializer child = new ChildBackgroundInitializer();
final AbstractChildBackgroundInitializer child = createChildBackgroundInitializer();
child.ex = new RuntimeException();
initializer.addInitializer(CHILD_INIT, child);
initializer.start();
@ -291,7 +291,7 @@ public class MultiBackgroundInitializerTest extends AbstractLangTest {
*/
@Test
public void testInitializeEx() throws ConcurrentException {
final ChildBackgroundInitializer child = new ChildBackgroundInitializer();
final AbstractChildBackgroundInitializer child = createChildBackgroundInitializer();
child.ex = new Exception();
initializer.addInitializer(CHILD_INIT, child);
initializer.start();
@ -312,7 +312,7 @@ public class MultiBackgroundInitializerTest extends AbstractLangTest {
@Test
public void testInitializeResultsIsSuccessfulTrue()
throws ConcurrentException {
final ChildBackgroundInitializer child = new ChildBackgroundInitializer();
final AbstractChildBackgroundInitializer child = createChildBackgroundInitializer();
initializer.addInitializer(CHILD_INIT, child);
initializer.start();
final MultiBackgroundInitializer.MultiBackgroundInitializerResults res = initializer
@ -329,7 +329,7 @@ public class MultiBackgroundInitializerTest extends AbstractLangTest {
@Test
public void testInitializeResultsIsSuccessfulFalse()
throws ConcurrentException {
final ChildBackgroundInitializer child = new ChildBackgroundInitializer();
final AbstractChildBackgroundInitializer child = createChildBackgroundInitializer();
child.ex = new Exception();
initializer.addInitializer(CHILD_INIT, child);
initializer.start();
@ -348,13 +348,13 @@ public class MultiBackgroundInitializerTest extends AbstractLangTest {
public void testInitializeNested() throws ConcurrentException {
final String nameMulti = "multiChildInitializer";
initializer
.addInitializer(CHILD_INIT, new ChildBackgroundInitializer());
.addInitializer(CHILD_INIT, createChildBackgroundInitializer());
final MultiBackgroundInitializer mi2 = new MultiBackgroundInitializer();
final int count = 3;
for (int i = 0; i < count; i++) {
mi2
.addInitializer(CHILD_INIT + i,
new ChildBackgroundInitializer());
createChildBackgroundInitializer());
}
initializer.addInitializer(nameMulti, mi2);
initializer.start();
@ -374,8 +374,8 @@ public class MultiBackgroundInitializerTest extends AbstractLangTest {
@Test
public void testIsInitialized()
throws ConcurrentException, InterruptedException {
final ChildBackgroundInitializer childOne = new ChildBackgroundInitializer();
final ChildBackgroundInitializer childTwo = new ChildBackgroundInitializer();
final AbstractChildBackgroundInitializer childOne = createChildBackgroundInitializer();
final AbstractChildBackgroundInitializer childTwo = createChildBackgroundInitializer();
childOne.enableLatch();
childTwo.enableLatch();
@ -409,14 +409,28 @@ public class MultiBackgroundInitializerTest extends AbstractLangTest {
}
/**
* A concrete implementation of {@code BackgroundInitializer} used for
* defining background tasks for {@code MultiBackgroundInitializer}.
* An overrideable method to create concrete implementations of
* {@code BackgroundInitializer} used for defining background tasks
* for {@code MultiBackgroundInitializer}.
*/
private static final class ChildBackgroundInitializer extends
BackgroundInitializer<Integer> {
protected AbstractChildBackgroundInitializer createChildBackgroundInitializer() {
return new MethodChildBackgroundInitializer();
}
/**
* A mostly complete implementation of {@code BackgroundInitializer} used for
* defining background tasks for {@code MultiBackgroundInitializer}.
*
* Subclasses will contain the initializer, either as an method implementation
* or by using a supplier.
*/
protected static class AbstractChildBackgroundInitializer extends BackgroundInitializer<CloseableCounter> {
/** Stores the current executor service. */
volatile ExecutorService currentExecutor;
/** An object containing the state we are testing */
CloseableCounter counter = new CloseableCounter();
/** A counter for the invocations of initialize(). */
volatile int initializeCalls;
@ -435,23 +449,76 @@ public class MultiBackgroundInitializerTest extends AbstractLangTest {
latch.countDown();
}
public CloseableCounter getCloseableCounter() {
return counter;
}
/**
* Records this invocation. Optionally throws an exception.
*/
@Override
protected Integer initialize() throws Exception {
currentExecutor = getActiveExecutor();
protected CloseableCounter initializeInternal() throws Exception {
initializeCalls++;
if (ex != null) {
throw ex;
}
currentExecutor = getActiveExecutor();
if (waitForLatch) {
latch.await();
}
return Integer.valueOf(initializeCalls);
if (ex != null) {
throw ex;
}
return counter.increment();
}
}
protected static class MethodChildBackgroundInitializer extends AbstractChildBackgroundInitializer {
@Override
protected CloseableCounter initialize() throws Exception {
return initializeInternal();
}
}
protected static class CloseableCounter {
/** The number of invocations of initialize(). */
volatile int initializeCalls;
/** Has the close consumer successfully reached this object. */
volatile boolean closed;
public CloseableCounter increment() {
initializeCalls++;
return this;
}
public int getInitializeCalls() {
return initializeCalls;
}
public CloseableCounter setInitializeCalls(int i) {
initializeCalls = i;
return this;
}
public void close() {
closed = true;
}
public boolean isClosed() {
return closed;
}
@Override
public boolean equals(final Object other) {
if (other instanceof CloseableCounter) {
return initializeCalls == ((CloseableCounter) other).getInitializeCalls();
}
return false;
}
// A convenience for testing that a CloseableCounter typed as Object has a specific initializeCalls value
public static CloseableCounter wrapInteger(int i) {
return new CloseableCounter().setInitializeCalls(i);
}
}
}