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:
parent
038e016d2a
commit
5d146cd069
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -17,6 +17,13 @@
|
||||||
|
|
||||||
package org.apache.commons.lang3.concurrent;
|
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.
|
* 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> {
|
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
|
* 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
|
* 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()}.
|
* 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
|
* @return the managed data object
|
||||||
* @throws E if an error occurs during object creation
|
* @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
|
* 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();
|
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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,9 @@ package org.apache.commons.lang3.concurrent;
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
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
|
* A specialized implementation of the {@link ConcurrentInitializer} interface
|
||||||
* based on an {@link AtomicReference} variable.
|
* based on an {@link AtomicReference} variable.
|
||||||
|
@ -62,13 +65,58 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||||
* @since 3.0
|
* @since 3.0
|
||||||
* @param <T> the type of the object managed by this initializer class
|
* @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();
|
private static final Object NO_INIT = new Object();
|
||||||
|
|
||||||
/** Holds the reference to the managed object. */
|
/** Holds the reference to the managed object. */
|
||||||
private final AtomicReference<T> reference = new AtomicReference<>(getNoInit());
|
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
|
* Returns the object managed by this initializer. The object is created if
|
||||||
* it is not available yet and stored internally. This method always returns
|
* 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() {
|
public boolean isInitialized() {
|
||||||
return reference.get() != NO_INIT;
|
return reference.get() != NO_INIT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected ConcurrentException getTypedException(Exception e) {
|
||||||
|
return new ConcurrentException(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,9 @@ package org.apache.commons.lang3.concurrent;
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
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
|
* A specialized {@link ConcurrentInitializer} implementation which is similar
|
||||||
* to {@link AtomicInitializer}, but ensures that the {@link #initialize()}
|
* to {@link AtomicInitializer}, but ensures that the {@link #initialize()}
|
||||||
|
@ -51,7 +54,24 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||||
* @since 3.0
|
* @since 3.0
|
||||||
* @param <T> the type of the object managed by this initializer class
|
* @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();
|
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. */
|
/** Holds the reference to the managed object. */
|
||||||
private final AtomicReference<T> reference = new AtomicReference<>(getNoInit());
|
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
|
* Gets (and initialize, if not initialized yet) the required object
|
||||||
*
|
*
|
||||||
|
@ -97,4 +145,12 @@ public abstract class AtomicSafeInitializer<T> extends AbstractConcurrentInitial
|
||||||
public boolean isInitialized() {
|
public boolean isInitialized() {
|
||||||
return reference.get() != NO_INIT;
|
return reference.get() != NO_INIT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected ConcurrentException getTypedException(Exception e) {
|
||||||
|
return new ConcurrentException(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,9 @@ import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.Future;
|
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.
|
* A class that allows complex initialization operations in a background task.
|
||||||
*
|
*
|
||||||
|
@ -82,7 +85,42 @@ import java.util.concurrent.Future;
|
||||||
* @since 3.0
|
* @since 3.0
|
||||||
* @param <T> the type of the object managed by this initializer class
|
* @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. */
|
/** The external executor service for executing tasks. */
|
||||||
private ExecutorService externalExecutor; // @GuardedBy("this")
|
private ExecutorService externalExecutor; // @GuardedBy("this")
|
||||||
|
@ -115,6 +153,29 @@ public abstract class BackgroundInitializer<T> extends AbstractConcurrentInitial
|
||||||
setExternalExecutor(exec);
|
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.
|
* 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -119,4 +119,13 @@ public class CallableBackgroundInitializer<T> extends BackgroundInitializer<T> {
|
||||||
private void checkCallable(final Callable<T> callable) {
|
private void checkCallable(final Callable<T> callable) {
|
||||||
Objects.requireNonNull(callable, "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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,29 +16,25 @@
|
||||||
*/
|
*/
|
||||||
package org.apache.commons.lang3.concurrent;
|
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
|
* This class provides a generic implementation of the lazy initialization pattern.
|
||||||
* pattern.
|
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* Sometimes an application has to deal with an object only under certain
|
* 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
|
||||||
* circumstances, e.g. when the user selects a specific menu item or if a
|
* 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
|
||||||
* special event is received. If the creation of the object is costly or the
|
* creation of this object until it is really needed. This is a use case for the lazy initialization pattern.
|
||||||
* 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>
|
||||||
* <p>
|
* <p>
|
||||||
* This abstract base class provides an implementation of the double-check idiom
|
* 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
|
||||||
* 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
|
||||||
* 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.
|
* actually creates the wrapped data object.
|
||||||
* </p>
|
* </p>
|
||||||
* <p>
|
* <p>
|
||||||
* As an usage example consider that we have a class {@code ComplexObject} whose
|
* 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
|
||||||
* instantiation is a complex operation. In order to apply lazy initialization
|
* this class, a subclass of {@link LazyInitializer} has to be created:
|
||||||
* to this class, a subclass of {@link LazyInitializer} has to be created:
|
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* <pre>
|
* <pre>
|
||||||
|
@ -51,9 +47,8 @@ package org.apache.commons.lang3.concurrent;
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* Access to the data object is provided through the {@code get()} method. So,
|
* 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
|
||||||
* code that wants to obtain the {@code ComplexObject} instance would simply
|
* like this:
|
||||||
* look like this:
|
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* <pre>
|
* <pre>
|
||||||
|
@ -65,32 +60,75 @@ package org.apache.commons.lang3.concurrent;
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* If multiple threads call the {@code get()} method when the object has not yet
|
* If multiple threads call the {@code get()} method when the object has not yet been created, they are blocked until initialization completes. The algorithm
|
||||||
* 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()}
|
||||||
* guarantees that only a single instance of the wrapped object class is
|
* method are pretty fast because no synchronization is needed (only an access to a <b>volatile</b> member field).
|
||||||
* 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>
|
* </p>
|
||||||
*
|
*
|
||||||
* @since 3.0
|
* @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();
|
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. */
|
/** Stores the managed object. */
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private volatile T object = (T) NO_INIT;
|
private volatile T object = (T) NO_INIT;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the object wrapped by this instance. On first access the object
|
* Constructs a new instance.
|
||||||
* is created. After that it is cached and can be accessed pretty fast.
|
*/
|
||||||
|
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}
|
* @return the object initialized by this {@link LazyInitializer}
|
||||||
* @throws ConcurrentException if an error occurred during initialization of
|
* @throws ConcurrentException if an error occurred during initialization of the object
|
||||||
* the object
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public T get() throws ConcurrentException {
|
public T get() throws ConcurrentException {
|
||||||
|
@ -121,4 +159,12 @@ public abstract class LazyInitializer<T> extends AbstractConcurrentInitializer<T
|
||||||
return object != NO_INIT;
|
return object != NO_INIT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected ConcurrentException getTypedException(Exception e) {
|
||||||
|
return new ConcurrentException(e);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -215,6 +215,39 @@ public class MultiBackgroundInitializer
|
||||||
return childInitializers.values().stream().allMatch(BackgroundInitializer::isInitialized);
|
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
|
* A data class for storing the results of the background initialization
|
||||||
* performed by {@link MultiBackgroundInitializer}. Objects of this inner
|
* performed by {@link MultiBackgroundInitializer}. Objects of this inner
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -29,6 +29,8 @@ import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
import org.apache.commons.lang3.AbstractLangTest;
|
import org.apache.commons.lang3.AbstractLangTest;
|
||||||
|
@ -42,10 +44,10 @@ public class BackgroundInitializerTest extends AbstractLangTest {
|
||||||
*
|
*
|
||||||
* @param init the initializer to test
|
* @param init the initializer to test
|
||||||
*/
|
*/
|
||||||
private void checkInitialize(final BackgroundInitializerTestImpl init) throws ConcurrentException {
|
private void checkInitialize(final AbstractBackgroundInitializerTestImpl init) throws ConcurrentException {
|
||||||
final Integer result = init.get();
|
final Integer result = init.get().getInitializeCalls();
|
||||||
assertEquals(1, result.intValue(), "Wrong result");
|
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");
|
assertNotNull(init.getFuture(), "No future");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,7 +56,7 @@ public class BackgroundInitializerTest extends AbstractLangTest {
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testInitialize() throws ConcurrentException {
|
public void testInitialize() throws ConcurrentException {
|
||||||
final BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl();
|
final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
|
||||||
init.start();
|
init.start();
|
||||||
checkInitialize(init);
|
checkInitialize(init);
|
||||||
}
|
}
|
||||||
|
@ -65,7 +67,7 @@ public class BackgroundInitializerTest extends AbstractLangTest {
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetActiveExecutorBeforeStart() {
|
public void testGetActiveExecutorBeforeStart() {
|
||||||
final BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl();
|
final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
|
||||||
assertNull(init.getActiveExecutor(), "Got an executor");
|
assertNull(init.getActiveExecutor(), "Got an executor");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,7 +78,7 @@ public class BackgroundInitializerTest extends AbstractLangTest {
|
||||||
public void testGetActiveExecutorExternal() throws InterruptedException, ConcurrentException {
|
public void testGetActiveExecutorExternal() throws InterruptedException, ConcurrentException {
|
||||||
final ExecutorService exec = Executors.newSingleThreadExecutor();
|
final ExecutorService exec = Executors.newSingleThreadExecutor();
|
||||||
try {
|
try {
|
||||||
final BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl(
|
final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl(
|
||||||
exec);
|
exec);
|
||||||
init.start();
|
init.start();
|
||||||
assertSame(exec, init.getActiveExecutor(), "Wrong executor");
|
assertSame(exec, init.getActiveExecutor(), "Wrong executor");
|
||||||
|
@ -92,7 +94,7 @@ public class BackgroundInitializerTest extends AbstractLangTest {
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetActiveExecutorTemp() throws ConcurrentException {
|
public void testGetActiveExecutorTemp() throws ConcurrentException {
|
||||||
final BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl();
|
final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
|
||||||
init.start();
|
init.start();
|
||||||
assertNotNull(init.getActiveExecutor(), "No active executor");
|
assertNotNull(init.getActiveExecutor(), "No active executor");
|
||||||
checkInitialize(init);
|
checkInitialize(init);
|
||||||
|
@ -104,7 +106,7 @@ public class BackgroundInitializerTest extends AbstractLangTest {
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testInitializeTempExecutor() throws ConcurrentException {
|
public void testInitializeTempExecutor() throws ConcurrentException {
|
||||||
final BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl();
|
final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
|
||||||
assertTrue(init.start(), "Wrong result of start()");
|
assertTrue(init.start(), "Wrong result of start()");
|
||||||
checkInitialize(init);
|
checkInitialize(init);
|
||||||
assertTrue(init.getActiveExecutor().isShutdown(), "Executor not shutdown");
|
assertTrue(init.getActiveExecutor().isShutdown(), "Executor not shutdown");
|
||||||
|
@ -118,7 +120,7 @@ public class BackgroundInitializerTest extends AbstractLangTest {
|
||||||
public void testSetExternalExecutor() throws ConcurrentException {
|
public void testSetExternalExecutor() throws ConcurrentException {
|
||||||
final ExecutorService exec = Executors.newCachedThreadPool();
|
final ExecutorService exec = Executors.newCachedThreadPool();
|
||||||
try {
|
try {
|
||||||
final BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl();
|
final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
|
||||||
init.setExternalExecutor(exec);
|
init.setExternalExecutor(exec);
|
||||||
assertEquals(exec, init.getExternalExecutor(), "Wrong executor service");
|
assertEquals(exec, init.getExternalExecutor(), "Wrong executor service");
|
||||||
assertTrue(init.start(), "Wrong result of start()");
|
assertTrue(init.start(), "Wrong result of start()");
|
||||||
|
@ -137,7 +139,7 @@ public class BackgroundInitializerTest extends AbstractLangTest {
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testSetExternalExecutorAfterStart() throws ConcurrentException, InterruptedException {
|
public void testSetExternalExecutorAfterStart() throws ConcurrentException, InterruptedException {
|
||||||
final BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl();
|
final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
|
||||||
init.start();
|
init.start();
|
||||||
final ExecutorService exec = Executors.newSingleThreadExecutor();
|
final ExecutorService exec = Executors.newSingleThreadExecutor();
|
||||||
try {
|
try {
|
||||||
|
@ -155,7 +157,7 @@ public class BackgroundInitializerTest extends AbstractLangTest {
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testStartMultipleTimes() throws ConcurrentException {
|
public void testStartMultipleTimes() throws ConcurrentException {
|
||||||
final BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl();
|
final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
|
||||||
assertTrue(init.start(), "Wrong result for start()");
|
assertTrue(init.start(), "Wrong result for start()");
|
||||||
for (int i = 0; i < 10; i++) {
|
for (int i = 0; i < 10; i++) {
|
||||||
assertFalse(init.start(), "Could start again");
|
assertFalse(init.start(), "Could start again");
|
||||||
|
@ -168,7 +170,7 @@ public class BackgroundInitializerTest extends AbstractLangTest {
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetBeforeStart() {
|
public void testGetBeforeStart() {
|
||||||
final BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl();
|
final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
|
||||||
assertThrows(IllegalStateException.class, init::get);
|
assertThrows(IllegalStateException.class, init::get);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,7 +180,7 @@ public class BackgroundInitializerTest extends AbstractLangTest {
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetRuntimeException() {
|
public void testGetRuntimeException() {
|
||||||
final BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl();
|
final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
|
||||||
final RuntimeException rex = new RuntimeException();
|
final RuntimeException rex = new RuntimeException();
|
||||||
init.ex = rex;
|
init.ex = rex;
|
||||||
init.start();
|
init.start();
|
||||||
|
@ -192,7 +194,7 @@ public class BackgroundInitializerTest extends AbstractLangTest {
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetCheckedException() {
|
public void testGetCheckedException() {
|
||||||
final BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl();
|
final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
|
||||||
final Exception ex = new Exception();
|
final Exception ex = new Exception();
|
||||||
init.ex = ex;
|
init.ex = ex;
|
||||||
init.start();
|
init.start();
|
||||||
|
@ -208,7 +210,7 @@ public class BackgroundInitializerTest extends AbstractLangTest {
|
||||||
@Test
|
@Test
|
||||||
public void testGetInterruptedException() throws InterruptedException {
|
public void testGetInterruptedException() throws InterruptedException {
|
||||||
final ExecutorService exec = Executors.newSingleThreadExecutor();
|
final ExecutorService exec = Executors.newSingleThreadExecutor();
|
||||||
final BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl(
|
final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl(
|
||||||
exec);
|
exec);
|
||||||
final CountDownLatch latch1 = new CountDownLatch(1);
|
final CountDownLatch latch1 = new CountDownLatch(1);
|
||||||
init.shouldSleep = true;
|
init.shouldSleep = true;
|
||||||
|
@ -242,7 +244,7 @@ public class BackgroundInitializerTest extends AbstractLangTest {
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testIsStartedFalse() {
|
public void testIsStartedFalse() {
|
||||||
final BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl();
|
final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
|
||||||
assertFalse(init.isStarted(), "Already started");
|
assertFalse(init.isStarted(), "Already started");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -251,7 +253,7 @@ public class BackgroundInitializerTest extends AbstractLangTest {
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testIsStartedTrue() {
|
public void testIsStartedTrue() {
|
||||||
final BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl();
|
final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
|
||||||
init.start();
|
init.start();
|
||||||
assertTrue(init.isStarted(), "Not started");
|
assertTrue(init.isStarted(), "Not started");
|
||||||
}
|
}
|
||||||
|
@ -261,7 +263,7 @@ public class BackgroundInitializerTest extends AbstractLangTest {
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testIsStartedAfterGet() throws ConcurrentException {
|
public void testIsStartedAfterGet() throws ConcurrentException {
|
||||||
final BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl();
|
final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
|
||||||
init.start();
|
init.start();
|
||||||
checkInitialize(init);
|
checkInitialize(init);
|
||||||
assertTrue(init.isStarted(), "Not started");
|
assertTrue(init.isStarted(), "Not started");
|
||||||
|
@ -272,7 +274,7 @@ public class BackgroundInitializerTest extends AbstractLangTest {
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testIsInitialized() throws ConcurrentException {
|
public void testIsInitialized() throws ConcurrentException {
|
||||||
final BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl();
|
final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
|
||||||
init.enableLatch();
|
init.enableLatch();
|
||||||
init.start();
|
init.start();
|
||||||
assertTrue(init.isStarted(), "Not started"); //Started and Initialized should return opposite values
|
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");
|
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
|
* A concrete implementation of BackgroundInitializer. It also overloads
|
||||||
* some methods that simplify testing.
|
* some methods that simplify testing.
|
||||||
*/
|
*/
|
||||||
private static final class BackgroundInitializerTestImpl extends
|
protected static class AbstractBackgroundInitializerTestImpl extends
|
||||||
BackgroundInitializer<Integer> {
|
BackgroundInitializer<CloseableCounter> {
|
||||||
/** An exception to be thrown by initialize(). */
|
/** An exception to be thrown by initialize(). */
|
||||||
Exception ex;
|
Exception ex;
|
||||||
|
|
||||||
/** A flag whether the background task should sleep a while. */
|
/** A flag whether the background task should sleep a while. */
|
||||||
boolean shouldSleep;
|
boolean shouldSleep;
|
||||||
|
|
||||||
/** The number of invocations of initialize(). */
|
|
||||||
volatile int initializeCalls;
|
|
||||||
|
|
||||||
/** A latch tests can use to control when initialize completes. */
|
/** A latch tests can use to control when initialize completes. */
|
||||||
final CountDownLatch latch = new CountDownLatch(1);
|
final CountDownLatch latch = new CountDownLatch(1);
|
||||||
boolean waitForLatch = false;
|
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);
|
super(exec);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -316,14 +326,17 @@ public class BackgroundInitializerTest extends AbstractLangTest {
|
||||||
latch.countDown();
|
latch.countDown();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CloseableCounter getCloseableCounter() {
|
||||||
|
return counter;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Records this invocation. Optionally throws an exception or sleeps a
|
* Records this invocation. Optionally throws an exception or sleeps a
|
||||||
* while.
|
* while.
|
||||||
*
|
*
|
||||||
* @throws Exception in case of an error
|
* @throws Exception in case of an error
|
||||||
*/
|
*/
|
||||||
@Override
|
protected CloseableCounter initializeInternal() throws Exception {
|
||||||
protected Integer initialize() throws Exception {
|
|
||||||
if (ex != null) {
|
if (ex != null) {
|
||||||
throw ex;
|
throw ex;
|
||||||
}
|
}
|
||||||
|
@ -333,7 +346,47 @@ public class BackgroundInitializerTest extends AbstractLangTest {
|
||||||
if (waitForLatch) {
|
if (waitForLatch) {
|
||||||
latch.await();
|
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -42,10 +42,10 @@ public class MultiBackgroundInitializerTest extends AbstractLangTest {
|
||||||
private static final String CHILD_INIT = "childInitializer";
|
private static final String CHILD_INIT = "childInitializer";
|
||||||
|
|
||||||
/** The initializer to be tested. */
|
/** The initializer to be tested. */
|
||||||
private MultiBackgroundInitializer initializer;
|
protected MultiBackgroundInitializer initializer;
|
||||||
|
|
||||||
/** A short time to wait for background threads to run. */
|
/** 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
|
@BeforeEach
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
|
@ -63,8 +63,8 @@ public class MultiBackgroundInitializerTest extends AbstractLangTest {
|
||||||
*/
|
*/
|
||||||
private void checkChild(final BackgroundInitializer<?> child,
|
private void checkChild(final BackgroundInitializer<?> child,
|
||||||
final ExecutorService expExec) throws ConcurrentException {
|
final ExecutorService expExec) throws ConcurrentException {
|
||||||
final ChildBackgroundInitializer cinit = (ChildBackgroundInitializer) child;
|
final AbstractChildBackgroundInitializer cinit = (AbstractChildBackgroundInitializer) child;
|
||||||
final Integer result = cinit.get();
|
final Integer result = cinit.get().getInitializeCalls();
|
||||||
assertEquals(1, result.intValue(), "Wrong result");
|
assertEquals(1, result.intValue(), "Wrong result");
|
||||||
assertEquals(1, cinit.initializeCalls, "Wrong number of executions");
|
assertEquals(1, cinit.initializeCalls, "Wrong number of executions");
|
||||||
if (expExec != null) {
|
if (expExec != null) {
|
||||||
|
@ -78,7 +78,7 @@ public class MultiBackgroundInitializerTest extends AbstractLangTest {
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testAddInitializerNullName() {
|
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;
|
final int count = 5;
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
initializer.addInitializer(CHILD_INIT + i,
|
initializer.addInitializer(CHILD_INIT + i,
|
||||||
new ChildBackgroundInitializer());
|
createChildBackgroundInitializer());
|
||||||
}
|
}
|
||||||
initializer.start();
|
initializer.start();
|
||||||
final MultiBackgroundInitializer.MultiBackgroundInitializerResults res = initializer
|
final MultiBackgroundInitializer.MultiBackgroundInitializerResults res = initializer
|
||||||
|
@ -126,7 +126,7 @@ public class MultiBackgroundInitializerTest extends AbstractLangTest {
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
final String key = CHILD_INIT + i;
|
final String key = CHILD_INIT + i;
|
||||||
assertTrue(res.initializerNames().contains(key), "Name not found: " + key);
|
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");
|
assertFalse(res.isException(key), "Exception flag");
|
||||||
assertNull(res.getException(key), "Got an exception");
|
assertNull(res.getException(key), "Got an exception");
|
||||||
checkChild(res.getInitializer(key), initializer.getActiveExecutor());
|
checkChild(res.getInitializer(key), initializer.getActiveExecutor());
|
||||||
|
@ -175,8 +175,8 @@ public class MultiBackgroundInitializerTest extends AbstractLangTest {
|
||||||
final String initExec = "childInitializerWithExecutor";
|
final String initExec = "childInitializerWithExecutor";
|
||||||
final ExecutorService exec = Executors.newSingleThreadExecutor();
|
final ExecutorService exec = Executors.newSingleThreadExecutor();
|
||||||
try {
|
try {
|
||||||
final ChildBackgroundInitializer c1 = new ChildBackgroundInitializer();
|
final AbstractChildBackgroundInitializer c1 = createChildBackgroundInitializer();
|
||||||
final ChildBackgroundInitializer c2 = new ChildBackgroundInitializer();
|
final AbstractChildBackgroundInitializer c2 = createChildBackgroundInitializer();
|
||||||
c2.setExternalExecutor(exec);
|
c2.setExternalExecutor(exec);
|
||||||
initializer.addInitializer(CHILD_INIT, c1);
|
initializer.addInitializer(CHILD_INIT, c1);
|
||||||
initializer.addInitializer(initExec, c2);
|
initializer.addInitializer(initExec, c2);
|
||||||
|
@ -201,7 +201,7 @@ public class MultiBackgroundInitializerTest extends AbstractLangTest {
|
||||||
initializer.start();
|
initializer.start();
|
||||||
assertThrows(
|
assertThrows(
|
||||||
IllegalStateException.class,
|
IllegalStateException.class,
|
||||||
() -> initializer.addInitializer(CHILD_INIT, new ChildBackgroundInitializer()),
|
() -> initializer.addInitializer(CHILD_INIT, createChildBackgroundInitializer()),
|
||||||
"Could add initializer after start()!");
|
"Could add initializer after start()!");
|
||||||
initializer.get();
|
initializer.get();
|
||||||
}
|
}
|
||||||
|
@ -275,7 +275,7 @@ public class MultiBackgroundInitializerTest extends AbstractLangTest {
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testInitializeRuntimeEx() {
|
public void testInitializeRuntimeEx() {
|
||||||
final ChildBackgroundInitializer child = new ChildBackgroundInitializer();
|
final AbstractChildBackgroundInitializer child = createChildBackgroundInitializer();
|
||||||
child.ex = new RuntimeException();
|
child.ex = new RuntimeException();
|
||||||
initializer.addInitializer(CHILD_INIT, child);
|
initializer.addInitializer(CHILD_INIT, child);
|
||||||
initializer.start();
|
initializer.start();
|
||||||
|
@ -291,7 +291,7 @@ public class MultiBackgroundInitializerTest extends AbstractLangTest {
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testInitializeEx() throws ConcurrentException {
|
public void testInitializeEx() throws ConcurrentException {
|
||||||
final ChildBackgroundInitializer child = new ChildBackgroundInitializer();
|
final AbstractChildBackgroundInitializer child = createChildBackgroundInitializer();
|
||||||
child.ex = new Exception();
|
child.ex = new Exception();
|
||||||
initializer.addInitializer(CHILD_INIT, child);
|
initializer.addInitializer(CHILD_INIT, child);
|
||||||
initializer.start();
|
initializer.start();
|
||||||
|
@ -312,7 +312,7 @@ public class MultiBackgroundInitializerTest extends AbstractLangTest {
|
||||||
@Test
|
@Test
|
||||||
public void testInitializeResultsIsSuccessfulTrue()
|
public void testInitializeResultsIsSuccessfulTrue()
|
||||||
throws ConcurrentException {
|
throws ConcurrentException {
|
||||||
final ChildBackgroundInitializer child = new ChildBackgroundInitializer();
|
final AbstractChildBackgroundInitializer child = createChildBackgroundInitializer();
|
||||||
initializer.addInitializer(CHILD_INIT, child);
|
initializer.addInitializer(CHILD_INIT, child);
|
||||||
initializer.start();
|
initializer.start();
|
||||||
final MultiBackgroundInitializer.MultiBackgroundInitializerResults res = initializer
|
final MultiBackgroundInitializer.MultiBackgroundInitializerResults res = initializer
|
||||||
|
@ -329,7 +329,7 @@ public class MultiBackgroundInitializerTest extends AbstractLangTest {
|
||||||
@Test
|
@Test
|
||||||
public void testInitializeResultsIsSuccessfulFalse()
|
public void testInitializeResultsIsSuccessfulFalse()
|
||||||
throws ConcurrentException {
|
throws ConcurrentException {
|
||||||
final ChildBackgroundInitializer child = new ChildBackgroundInitializer();
|
final AbstractChildBackgroundInitializer child = createChildBackgroundInitializer();
|
||||||
child.ex = new Exception();
|
child.ex = new Exception();
|
||||||
initializer.addInitializer(CHILD_INIT, child);
|
initializer.addInitializer(CHILD_INIT, child);
|
||||||
initializer.start();
|
initializer.start();
|
||||||
|
@ -348,13 +348,13 @@ public class MultiBackgroundInitializerTest extends AbstractLangTest {
|
||||||
public void testInitializeNested() throws ConcurrentException {
|
public void testInitializeNested() throws ConcurrentException {
|
||||||
final String nameMulti = "multiChildInitializer";
|
final String nameMulti = "multiChildInitializer";
|
||||||
initializer
|
initializer
|
||||||
.addInitializer(CHILD_INIT, new ChildBackgroundInitializer());
|
.addInitializer(CHILD_INIT, createChildBackgroundInitializer());
|
||||||
final MultiBackgroundInitializer mi2 = new MultiBackgroundInitializer();
|
final MultiBackgroundInitializer mi2 = new MultiBackgroundInitializer();
|
||||||
final int count = 3;
|
final int count = 3;
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
mi2
|
mi2
|
||||||
.addInitializer(CHILD_INIT + i,
|
.addInitializer(CHILD_INIT + i,
|
||||||
new ChildBackgroundInitializer());
|
createChildBackgroundInitializer());
|
||||||
}
|
}
|
||||||
initializer.addInitializer(nameMulti, mi2);
|
initializer.addInitializer(nameMulti, mi2);
|
||||||
initializer.start();
|
initializer.start();
|
||||||
|
@ -374,8 +374,8 @@ public class MultiBackgroundInitializerTest extends AbstractLangTest {
|
||||||
@Test
|
@Test
|
||||||
public void testIsInitialized()
|
public void testIsInitialized()
|
||||||
throws ConcurrentException, InterruptedException {
|
throws ConcurrentException, InterruptedException {
|
||||||
final ChildBackgroundInitializer childOne = new ChildBackgroundInitializer();
|
final AbstractChildBackgroundInitializer childOne = createChildBackgroundInitializer();
|
||||||
final ChildBackgroundInitializer childTwo = new ChildBackgroundInitializer();
|
final AbstractChildBackgroundInitializer childTwo = createChildBackgroundInitializer();
|
||||||
|
|
||||||
childOne.enableLatch();
|
childOne.enableLatch();
|
||||||
childTwo.enableLatch();
|
childTwo.enableLatch();
|
||||||
|
@ -409,14 +409,28 @@ public class MultiBackgroundInitializerTest extends AbstractLangTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A concrete implementation of {@code BackgroundInitializer} used for
|
* An overrideable method to create concrete implementations of
|
||||||
* defining background tasks for {@code MultiBackgroundInitializer}.
|
* {@code BackgroundInitializer} used for defining background tasks
|
||||||
|
* for {@code MultiBackgroundInitializer}.
|
||||||
*/
|
*/
|
||||||
private static final class ChildBackgroundInitializer extends
|
protected AbstractChildBackgroundInitializer createChildBackgroundInitializer() {
|
||||||
BackgroundInitializer<Integer> {
|
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. */
|
/** Stores the current executor service. */
|
||||||
volatile ExecutorService currentExecutor;
|
volatile ExecutorService currentExecutor;
|
||||||
|
|
||||||
|
/** An object containing the state we are testing */
|
||||||
|
CloseableCounter counter = new CloseableCounter();
|
||||||
|
|
||||||
/** A counter for the invocations of initialize(). */
|
/** A counter for the invocations of initialize(). */
|
||||||
volatile int initializeCalls;
|
volatile int initializeCalls;
|
||||||
|
|
||||||
|
@ -435,23 +449,76 @@ public class MultiBackgroundInitializerTest extends AbstractLangTest {
|
||||||
latch.countDown();
|
latch.countDown();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CloseableCounter getCloseableCounter() {
|
||||||
|
return counter;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Records this invocation. Optionally throws an exception.
|
* Records this invocation. Optionally throws an exception.
|
||||||
*/
|
*/
|
||||||
@Override
|
protected CloseableCounter initializeInternal() throws Exception {
|
||||||
protected Integer initialize() throws Exception {
|
|
||||||
currentExecutor = getActiveExecutor();
|
|
||||||
initializeCalls++;
|
initializeCalls++;
|
||||||
|
currentExecutor = getActiveExecutor();
|
||||||
if (ex != null) {
|
|
||||||
throw ex;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (waitForLatch) {
|
if (waitForLatch) {
|
||||||
latch.await();
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue