Merge branch 'gh-11584'

pull/11839/merge
Phillip Webb 7 years ago
commit 798882bd3f

@ -26,12 +26,10 @@ import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.boot.actuate.endpoint.EndpointFilter;
@ -41,6 +39,7 @@ import org.springframework.boot.actuate.endpoint.Operation;
import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker;
import org.springframework.boot.actuate.endpoint.invoke.OperationInvokerAdvisor;
import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper;
import org.springframework.boot.util.LambdaSafe;
import org.springframework.context.ApplicationContext;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotatedElementUtils;
@ -66,8 +65,6 @@ import org.springframework.util.StringUtils;
public abstract class EndpointDiscoverer<E extends ExposableEndpoint<O>, O extends Operation>
implements EndpointsSupplier<E> {
private static final Log logger = LogFactory.getLog(EndpointDiscoverer.class);
private final ApplicationContext applicationContext;
private final Collection<EndpointFilter<E>> filters;
@ -313,23 +310,15 @@ public abstract class EndpointDiscoverer<E extends ExposableEndpoint<O>, O exten
return isFilterMatch(filter, getFilterEndpoint(endpointBean));
}
@SuppressWarnings("unchecked")
private boolean isFilterMatch(EndpointFilter<E> filter, E endpoint) {
try {
return filter.match(endpoint);
}
catch (ClassCastException ex) {
String msg = ex.getMessage();
if (msg == null || msg.startsWith(endpoint.getClass().getName())) {
// Possibly a lambda-defined EndpointFilter which we could not resolve the
// generic EndpointInfo type for
if (logger.isDebugEnabled()) {
logger.debug("Non-matching Endpoint for EndpointFilter: " + filter,
ex);
}
return false;
}
throw ex;
}
return LambdaSafe.callback(EndpointFilter.class, filter, endpoint)
.withLogger(EndpointDiscoverer.class).invokeAnd((f) -> f.match(endpoint))
.get();
}
public <A, B> void doIt(Function<A, B> x) {
}
private E getFilterEndpoint(EndpointBean endpointBean) {

@ -17,16 +17,15 @@
package org.springframework.boot.actuate.metrics.cache;
import java.util.Collection;
import java.util.Objects;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.Tags;
import io.micrometer.core.instrument.binder.MeterBinder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.util.LambdaSafe;
import org.springframework.cache.Cache;
import org.springframework.core.ResolvableType;
/**
* Register supported {@link Cache} to a {@link MeterRegistry}.
@ -36,8 +35,6 @@ import org.springframework.core.ResolvableType;
*/
public class CacheMetricsRegistrar {
private static final Log logger = LogFactory.getLog(CacheMetricsRegistrar.class);
private final MeterRegistry registry;
private final String metricName;
@ -74,41 +71,15 @@ public class CacheMetricsRegistrar {
return false;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@SuppressWarnings({ "unchecked" })
private MeterBinder getMeterBinder(Cache cache, Tags tags) {
tags = tags.and(getAdditionalTags(cache));
for (CacheMeterBinderProvider<?> binderProvider : this.binderProviders) {
Class<?> cacheType = ResolvableType
.forClass(CacheMeterBinderProvider.class, binderProvider.getClass())
.resolveGeneric();
if (cacheType.isInstance(cache)) {
try {
MeterBinder meterBinder = ((CacheMeterBinderProvider) binderProvider)
.getMeterBinder(cache, this.metricName, tags);
if (meterBinder != null) {
return meterBinder;
}
}
catch (ClassCastException ex) {
String msg = ex.getMessage();
if (msg == null || msg.startsWith(cache.getClass().getName())) {
// Possibly a lambda-defined CacheMeterBinderProvider which we
// could not resolve the generic Cache type for
if (logger.isDebugEnabled()) {
logger.debug(
"Non-matching Cache type for CacheMeterBinderProvider: "
+ binderProvider,
ex);
}
}
else {
throw ex;
}
}
}
}
return null;
Tags cacheTags = tags.and(getAdditionalTags(cache));
return LambdaSafe
.callbacks(CacheMeterBinderProvider.class, this.binderProviders, cache)
.withLogger(CacheMetricsRegistrar.class)
.invokeAnd((binderProvider) -> binderProvider.getMeterBinder(cache,
this.metricName, cacheTags))
.filter(Objects::nonNull).findFirst().orElse(null);
}
/**

@ -20,11 +20,8 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.util.LambdaSafe;
import org.springframework.cache.CacheManager;
import org.springframework.core.ResolvableType;
/**
* Invokes the available {@link CacheManagerCustomizer} instances in the context for a
@ -35,8 +32,6 @@ import org.springframework.core.ResolvableType;
*/
public class CacheManagerCustomizers {
private static final Log logger = LogFactory.getLog(CacheManagerCustomizers.class);
private final List<CacheManagerCustomizer<?>> customizers;
public CacheManagerCustomizers(
@ -53,32 +48,12 @@ public class CacheManagerCustomizers {
* @param cacheManager the cache manager to customize
* @return the cache manager
*/
@SuppressWarnings("unchecked")
public <T extends CacheManager> T customize(T cacheManager) {
for (CacheManagerCustomizer<?> customizer : this.customizers) {
Class<?> generic = ResolvableType
.forClass(CacheManagerCustomizer.class, customizer.getClass())
.resolveGeneric();
if (generic.isInstance(cacheManager)) {
customize(cacheManager, customizer);
}
}
LambdaSafe.callbacks(CacheManagerCustomizer.class, this.customizers, cacheManager)
.withLogger(CacheManagerCustomizers.class)
.invoke((customizer) -> customizer.customize(cacheManager));
return cacheManager;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private void customize(CacheManager cacheManager, CacheManagerCustomizer customizer) {
try {
customizer.customize(cacheManager);
}
catch (ClassCastException ex) {
// Possibly a lambda-defined customizer which we could not resolve the generic
// cache manager type for
if (logger.isDebugEnabled()) {
logger.debug(
"Non-matching cache manager type for customizer: " + customizer,
ex);
}
}
}
}

@ -18,12 +18,10 @@ package org.springframework.boot.autoconfigure.transaction;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.ResolvableType;
import org.springframework.boot.util.LambdaSafe;
import org.springframework.transaction.PlatformTransactionManager;
/**
@ -34,44 +32,21 @@ import org.springframework.transaction.PlatformTransactionManager;
*/
public class TransactionManagerCustomizers {
private static final Log logger = LogFactory
.getLog(TransactionManagerCustomizers.class);
private final List<PlatformTransactionManagerCustomizer<?>> customizers;
public TransactionManagerCustomizers(
Collection<? extends PlatformTransactionManagerCustomizer<?>> customizers) {
this.customizers = (customizers == null ? null : new ArrayList<>(customizers));
this.customizers = (customizers == null ? Collections.emptyList()
: new ArrayList<>(customizers));
}
@SuppressWarnings("unchecked")
public void customize(PlatformTransactionManager transactionManager) {
if (this.customizers != null) {
for (PlatformTransactionManagerCustomizer<?> customizer : this.customizers) {
Class<?> generic = ResolvableType
.forClass(PlatformTransactionManagerCustomizer.class,
customizer.getClass())
.resolveGeneric();
if (generic.isInstance(transactionManager)) {
customize(transactionManager, customizer);
}
}
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private void customize(PlatformTransactionManager transactionManager,
PlatformTransactionManagerCustomizer customizer) {
try {
customizer.customize(transactionManager);
}
catch (ClassCastException ex) {
// Possibly a lambda-defined customizer which we could not resolve the generic
// transaction manager type for
if (logger.isDebugEnabled()) {
logger.debug("Non-matching transaction manager type for customizer: "
+ customizer, ex);
}
}
LambdaSafe
.callbacks(PlatformTransactionManagerCustomizer.class, this.customizers,
transactionManager)
.withLogger(TransactionManagerCustomizers.class)
.invoke((customizer) -> customizer.customize(transactionManager));
}
}

@ -0,0 +1,402 @@
/*
* Copyright 2012-2018 the original author or authors.
*
* Licensed 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.springframework.boot.util;
import java.util.Collection;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Stream;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.ResolvableType;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* Utility that can be used to invoke lambdas in a safe way. Primarily designed to help
* support generically typed callbacks where {@link ClassCastException class cast
* exceptions} need to be dealt with due to class erasure.
*
* @author Phillip Webb
* @since 2.0.0
*/
public final class LambdaSafe {
private LambdaSafe() {
}
/**
* Start a call to a single callback instance, dealing with common generic type
* concerns and exceptions.
* @param callbackType the callback type (a {@link FunctionalInterface functional
* interface})
* @param callbackInstance the callback instance (may be a lambda)
* @param argument the primary argument passed to the callback
* @param additionalArguments any additional argument passed to the callback
* @param <C> the callback type
* @param <A> the primary argument type
* @return a {@link Callback} instance that can be invoked.
*/
public static <C, A> Callback<C, A> callback(Class<C> callbackType,
C callbackInstance, A argument, Object... additionalArguments) {
Assert.notNull(callbackType, "CallbackType must not be null");
Assert.notNull(callbackInstance, "CallbackInstance must not be null");
return new Callback<>(callbackType, callbackInstance, argument,
additionalArguments);
}
/**
* Start a call to a single callback instance, dealing with common generic type
* concerns and exceptions.
* @param callbackType the callback type (a {@link FunctionalInterface functional
* interface})
* @param callbackInstances the callback instances (elements may be lambdas)
* @param argument the primary argument passed to the callbacks
* @param additionalArguments any additional argument passed to the callbacks
* @param <C> the callback type
* @param <A> the primary argument type
* @return a {@link Callbacks} instance that can be invoked.
*/
public static <C, A> Callbacks<C, A> callbacks(Class<C> callbackType,
Collection<? extends C> callbackInstances, A argument,
Object... additionalArguments) {
Assert.notNull(callbackType, "CallbackType must not be null");
Assert.notNull(callbackInstances, "CallbackInstances must not be null");
return new Callbacks<C, A>(callbackType, callbackInstances, argument,
additionalArguments);
}
/**
* Abstract base class for lambda safe callbacks.
*/
private static abstract class LambdaSafeCallback<C, A, SELF extends LambdaSafeCallback<C, A, SELF>> {
private final Class<C> callbackType;
private final A argument;
private final Object[] additionalArguments;
private Log logger;
private Filter<C, A> filter = new GenericTypeFilter<C, A>();
protected LambdaSafeCallback(Class<C> callbackType, A argument,
Object[] additionalArguments) {
this.callbackType = callbackType;
this.argument = argument;
this.additionalArguments = additionalArguments;
this.logger = LogFactory.getLog(callbackType);
}
/**
* Use the specified logger source to report any lambda failures.
* @param loggerSource the logger source to use
* @return this instance
*/
public SELF withLogger(Class<?> loggerSource) {
return withLogger(LogFactory.getLog(loggerSource));
}
/**
* Use the specified logger to report any lambda failures.
* @param logger the logger to use
* @return this instance
*/
public SELF withLogger(Log logger) {
Assert.notNull(logger, "Logger must not be null");
this.logger = logger;
return self();
}
/**
* Use a specific filter to determine when a callback should apply. If not
* explicit filter is set filter will be attempted using the generic type on the
* callback type.
* @param filter the filter to use
* @return this instance
*/
public SELF withFilter(Filter<C, A> filter) {
Assert.notNull(filter, "Filter must not be null");
this.filter = filter;
return self();
}
protected final <R> InvocationResult<R> invoke(C callbackInstance,
Supplier<R> supplier) {
if (this.filter.match(this.callbackType, callbackInstance, this.argument,
this.additionalArguments)) {
try {
return InvocationResult.of(supplier.get());
}
catch (ClassCastException ex) {
if (!isLambdaGenericProblem(ex)) {
throw ex;
}
logNonMachingType(callbackInstance, ex);
}
}
return InvocationResult.noResult();
}
private boolean isLambdaGenericProblem(ClassCastException ex) {
return (ex.getMessage() == null
|| startsWithArgumentClassName(ex.getMessage()));
}
private boolean startsWithArgumentClassName(String message) {
Predicate<Object> startsWith = (argument) -> argument != null
&& message.startsWith(argument.getClass().getName());
return startsWith.test(this.argument)
|| Stream.of(this.additionalArguments).anyMatch(startsWith);
}
private void logNonMachingType(C callback, ClassCastException ex) {
if (this.logger.isDebugEnabled()) {
Class<?> expectedType = ResolvableType.forClass(this.callbackType)
.resolveGeneric();
String message = "Non-matching "
+ (expectedType == null ? "type"
: ClassUtils.getShortName(expectedType) + " type")
+ " for callback " + ClassUtils.getShortName(this.callbackType)
+ ": " + callback;
this.logger.debug(message, ex);
}
}
@SuppressWarnings("unchecked")
private SELF self() {
return (SELF) this;
}
}
/**
* Represents a single callback that can be invoked in a lambda safe way.
*
* @param <C> the callback type
* @param <A> the primary argument type
*/
public static final class Callback<C, A>
extends LambdaSafeCallback<C, A, Callback<C, A>> {
private C callbackInstance;
private Callback(Class<C> callbackType, C callbackInstance, A argument,
Object[] additionalArguments) {
super(callbackType, argument, additionalArguments);
this.callbackInstance = callbackInstance;
}
/**
* Invoke the callback instance where the callback method returns void.
* @param invoker the invoker used to invoke the callback
*/
public void invoke(Consumer<C> invoker) {
invoke(this.callbackInstance, () -> {
invoker.accept(this.callbackInstance);
return null;
});
}
/**
* Invoke the callback instance where the callback method returns a result.
* @param invoker the invoker used to invoke the callback
* @param <R> the result type
* @return the result of the invocation (may be {@link InvocationResult#noResult}
* if the callback was not invoked)
*/
public <R> InvocationResult<R> invokeAnd(Function<C, R> invoker) {
return invoke(this.callbackInstance,
() -> invoker.apply(this.callbackInstance));
}
}
/**
* Represents a collection of callbacks that can be invoked in a lambda safe way.
*
* @param <C> the callback type
* @param <A> the primary argument type
*/
public static final class Callbacks<C, A>
extends LambdaSafeCallback<C, A, Callbacks<C, A>> {
private Collection<? extends C> callbackInstances;
private Callbacks(Class<C> callbackType,
Collection<? extends C> callbackInstances, A argument,
Object[] additionalArguments) {
super(callbackType, argument, additionalArguments);
this.callbackInstances = callbackInstances;
}
/**
* Invoke the callback instances where the callback method returns void.
* @param invoker the invoker used to invoke the callback
*/
public void invoke(Consumer<C> invoker) {
Function<C, InvocationResult<Void>> mapper = (callbackInstance) -> invoke(
callbackInstance, () -> {
invoker.accept(callbackInstance);
return null;
});
this.callbackInstances.stream().map(mapper).forEach((result) -> {
});
}
/**
* Invoke the callback instances where the callback method returns a result.
* @param invoker the invoker used to invoke the callback
* @param <R> the result type
* @return the results of the invocation (may be an empty stream if not callbacks
* could be called)
*/
public <R> Stream<R> invokeAnd(Function<C, R> invoker) {
Function<C, InvocationResult<R>> mapper = (callbackInstance) -> invoke(
callbackInstance, () -> invoker.apply(callbackInstance));
return this.callbackInstances.stream().map(mapper)
.filter(InvocationResult::hasResult).map(InvocationResult::get);
}
}
/**
* A filter that can be used to restrict when a callback is used.
*
* @param <C> the callback type
* @param <A> the primary argument type
*/
@FunctionalInterface
interface Filter<C, A> {
/**
* Determine if the given callback matches and should be invoked.
* @param callbackType the callback type (the functional interface)
* @param callbackInstance the callback instance (the implementation)
* @param argument the primary argument
* @param additionalArguments any additional arguments
* @return if the callback matches and should be invoked
*/
boolean match(Class<C> callbackType, C callbackInstance, A argument,
Object[] additionalArguments);
/**
* Return a {@link Filter} that allows all callbacks to be invoked.
* @param <C> the callback type
* @param <A> the primary argument type
* @return an "allow all" filter
*/
static <C, A> Filter<C, A> allowAll() {
return (callbackType, callbackInstance, argument,
additionalArguments) -> true;
}
}
/**
* {@link Filter} that matches when the callback has a single generic and primary
* argument is an instance of it.
*/
private static class GenericTypeFilter<C, A> implements Filter<C, A> {
@Override
public boolean match(Class<C> callbackType, C callbackInstance, A argument,
Object[] additionalArguments) {
ResolvableType type = ResolvableType.forClass(callbackType,
callbackInstance.getClass());
if (type.getGenerics().length == 1 && type.resolveGeneric() != null) {
return type.resolveGeneric().isInstance(argument);
}
return true;
}
}
/**
* The result of a callback which may be a value, {@code null} or absent entirely if
* the callback wasn't suitable. Similar in design to {@link Optional} but allows for
* {@code null} as a valid value.
* @param <R> The result type
*/
public final static class InvocationResult<R> {
private static final InvocationResult<?> NONE = new InvocationResult<Object>(
null);
private final R value;
private InvocationResult(R value) {
this.value = value;
}
/**
* Return true if a result in present.
* @return if a result is present
*/
public boolean hasResult() {
return this != NONE;
}
/**
* Return the result of the invocation or {@code null} if the callback wasn't
* suitable.
* @return the result of the invocation or {@code null}
*/
public R get() {
return this.value;
}
/**
* Return the result of the invocation or the given fallback if the callback
* wasn't suitable.
* @param fallback the fallback to use when there is no result
* @return the result of the invocation or the fallback
*/
public R get(R fallback) {
return (this == NONE ? fallback : this.value);
}
/**
* Create a new {@link InvocationResult} instance with the specified value.
* @param value the value (may be {@code null})
* @param <R> the result type
* @return an {@link InvocationResult}
*/
public static <R> InvocationResult<R> of(R value) {
return new InvocationResult<R>(value);
}
/**
* Return an {@link InvocationResult} instance representing no result.
* @param <R> the result type
* @return an {@link InvocationResult}
*/
@SuppressWarnings("unchecked")
public static <R> InvocationResult<R> noResult() {
return (InvocationResult<R>) NONE;
}
}
}

@ -0,0 +1,20 @@
/*
* Copyright 2012-2018 the original author or authors.
*
* Licensed 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.
*/
/**
* Contains miscellaneous utility classes.
*/
package org.springframework.boot.util;

@ -21,15 +21,12 @@ import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.ResolvableType;
import org.springframework.boot.util.LambdaSafe;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.util.Assert;
@ -45,9 +42,6 @@ import org.springframework.util.Assert;
public class WebServerFactoryCustomizerBeanPostProcessor
implements BeanPostProcessor, BeanFactoryAware {
private static final Log logger = LogFactory
.getLog(WebServerFactoryCustomizerBeanPostProcessor.class);
private ListableBeanFactory beanFactory;
private List<WebServerFactoryCustomizer<?>> customizers;
@ -75,45 +69,13 @@ public class WebServerFactoryCustomizerBeanPostProcessor
return bean;
}
private void postProcessBeforeInitialization(WebServerFactory bean) {
for (WebServerFactoryCustomizer<?> customizer : getCustomizers()) {
Class<?> type = ResolvableType
.forClass(WebServerFactoryCustomizer.class, customizer.getClass())
.getGeneric().resolve(WebServerFactory.class);
if (type.isInstance(bean)) {
invokeCustomizer(customizer, bean);
}
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private void invokeCustomizer(WebServerFactoryCustomizer customizer,
WebServerFactory webServerFactory) {
try {
customizer.customize(webServerFactory);
}
catch (ClassCastException ex) {
String msg = ex.getMessage();
if (msg == null || msg.startsWith(webServerFactory.getClass().getName())) {
// Possibly a lambda-defined WebServerFactoryCustomizer which we could not
// resolve the
// generic WebServerFactory type for
logLambdaDebug(customizer, ex);
}
else {
throw ex;
}
}
}
private void logLambdaDebug(WebServerFactoryCustomizer<?> customizer,
ClassCastException ex) {
if (logger.isDebugEnabled()) {
logger.debug(
"Non-matching WebServerFactory type for WebServerFactoryCustomizer: "
+ customizer,
ex);
}
@SuppressWarnings("unchecked")
private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
LambdaSafe
.callbacks(WebServerFactoryCustomizer.class, getCustomizers(),
webServerFactory)
.withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)
.invoke((customizer) -> customizer.customize(webServerFactory));
}
private Collection<WebServerFactoryCustomizer<?>> getCustomizers() {

@ -0,0 +1,475 @@
/*
* Copyright 2012-2018 the original author or authors.
*
* Licensed 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.springframework.boot.util;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Stream;
import org.apache.commons.logging.Log;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.boot.util.LambdaSafe.Filter;
import org.springframework.boot.util.LambdaSafe.InvocationResult;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.contains;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
/**
* Tests for {@link LambdaSafe}.
*
* @author Phillip Webb
*/
public class LambdaSafeTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void callbackWhenCallbackTypeIsNullShouldThrowException() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("CallbackType must not be null");
LambdaSafe.callback(null, new Object(), null);
}
@Test
public void callbackWhenCallbackInstanceIsNullShouldThrowException() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("CallbackInstance must not be null");
LambdaSafe.callback(Object.class, null, null);
}
@Test
public void callbackInvokeWhenNoGenericShouldInvokeCallback() {
NonGenericCallback callbackInstance = mock(NonGenericCallback.class);
String argument = "foo";
LambdaSafe.callback(NonGenericCallback.class, callbackInstance, argument)
.invoke((c) -> c.handle(argument));
verify(callbackInstance).handle(argument);
}
@Test
@SuppressWarnings("unchecked")
public void callbackInvokeWhenHasGenericShouldInvokeCallback() {
StringCallback callbackInstance = mock(StringCallback.class);
String argument = "foo";
LambdaSafe.callback(GenericCallback.class, callbackInstance, argument)
.invoke((c) -> c.handle(argument));
verify(callbackInstance).handle(argument);
}
@Test
@SuppressWarnings("unchecked")
public void callbackInvokeWhenHasResolvableGenericMatchShouldInvokeCallback() {
StringBuilderCallback callbackInstance = mock(StringBuilderCallback.class);
StringBuilder argument = new StringBuilder("foo");
LambdaSafe.callback(GenericCallback.class, callbackInstance, argument)
.invoke((c) -> c.handle(argument));
verify(callbackInstance).handle(argument);
}
@Test
@SuppressWarnings("unchecked")
public void callbackInvokeWhenHasResolvableGenericNonMatchShouldNotInvokeCallback() {
GenericCallback<?> callbackInstance = mock(StringBuilderCallback.class);
String argument = "foo";
LambdaSafe.callback(GenericCallback.class, callbackInstance, argument)
.invoke((c) -> c.handle(argument));
verifyZeroInteractions(callbackInstance);
}
@Test
@SuppressWarnings("unchecked")
public void callbackInvokeWhenLambdaMismatchShouldSwallowException() {
GenericCallback<StringBuilder> callbackInstance = (s) -> {
fail("Should not get here");
};
String argument = "foo";
LambdaSafe.callback(GenericCallback.class, callbackInstance, argument)
.invoke((c) -> c.handle(argument));
}
@Test
@SuppressWarnings("unchecked")
public void callbackInvokeWhenLambdaMismatchOnDifferentArgumentShouldSwallowException() {
GenericMultiArgCallback<StringBuilder> callbackInstance = (n, s, b) -> {
fail("Should not get here");
};
String argument = "foo";
LambdaSafe.callback(GenericMultiArgCallback.class, callbackInstance, argument)
.invoke((c) -> c.handle(1, argument, false));
}
@Test
public void callbackInvokeAndWhenNoGenericShouldReturnResult() {
NonGenericFactory callbackInstance = mock(NonGenericFactory.class);
String argument = "foo";
given(callbackInstance.handle("foo")).willReturn(123);
InvocationResult<Integer> result = LambdaSafe
.callback(NonGenericFactory.class, callbackInstance, argument)
.invokeAnd((c) -> c.handle(argument));
assertThat(result.hasResult()).isTrue();
assertThat(result.get()).isEqualTo(123);
}
@Test
@SuppressWarnings("unchecked")
public void callbackInvokeAndWhenHasGenericShouldReturnResult() {
StringFactory callbackInstance = mock(StringFactory.class);
String argument = "foo";
given(callbackInstance.handle("foo")).willReturn(123);
InvocationResult<Integer> result = LambdaSafe
.callback(GenericFactory.class, callbackInstance, argument)
.invokeAnd((c) -> c.handle(argument));
assertThat(result.hasResult()).isTrue();
assertThat(result.get()).isEqualTo(123);
}
@Test
@SuppressWarnings("unchecked")
public void callbackInvokeAndWhenReturnNullShouldReturnResult() {
StringFactory callbackInstance = mock(StringFactory.class);
String argument = "foo";
given(callbackInstance.handle("foo")).willReturn(null);
InvocationResult<Integer> result = LambdaSafe
.callback(GenericFactory.class, callbackInstance, argument)
.invokeAnd((c) -> c.handle(argument));
assertThat(result.hasResult()).isTrue();
assertThat(result.get()).isNull();
}
@Test
@SuppressWarnings("unchecked")
public void callbackInvokeAndWhenHasResolvableGenericMatchShouldReturnResult() {
StringBuilderFactory callbackInstance = mock(StringBuilderFactory.class);
StringBuilder argument = new StringBuilder("foo");
given(callbackInstance.handle(any(StringBuilder.class))).willReturn(123);
InvocationResult<Integer> result = LambdaSafe
.callback(GenericFactory.class, callbackInstance, argument)
.invokeAnd((c) -> c.handle(argument));
verify(callbackInstance).handle(argument);
assertThat(result.hasResult()).isTrue();
assertThat(result.get()).isEqualTo(123);
}
@Test
@SuppressWarnings("unchecked")
public void callbackInvokeAndWhenHasResolvableGenericNonMatchShouldReturnNoResult() {
GenericFactory<?> callbackInstance = mock(StringBuilderFactory.class);
String argument = "foo";
InvocationResult<Integer> result = LambdaSafe
.callback(GenericFactory.class, callbackInstance, argument)
.invokeAnd((c) -> c.handle(argument));
assertThat(result.hasResult()).isFalse();
verifyZeroInteractions(callbackInstance);
}
@Test
@SuppressWarnings("unchecked")
public void callbackInvokeAndWhenLambdaMismatchShouldSwallowException() {
GenericFactory<StringBuilder> callbackInstance = (s) -> {
fail("Should not get here");
return 123;
};
String argument = "foo";
InvocationResult<Integer> result = LambdaSafe
.callback(GenericFactory.class, callbackInstance, argument)
.invokeAnd((c) -> c.handle(argument));
assertThat(result.hasResult()).isFalse();
}
@Test
@SuppressWarnings("unchecked")
public void callbackInvokeAndWhenLambdaMismatchOnDifferentArgumentShouldSwallowException() {
GenericMultiArgFactory<StringBuilder> callbackInstance = (n, s, b) -> {
fail("Should not get here");
return 123;
};
String argument = "foo";
InvocationResult<Integer> result = LambdaSafe
.callback(GenericMultiArgFactory.class, callbackInstance, argument)
.invokeAnd((c) -> c.handle(1, argument, false));
assertThat(result.hasResult()).isFalse();
}
@Test
public void callbacksInvokeWhenNoGenericShouldInvokeCallbacks() {
NonGenericCallback callbackInstance = mock(NonGenericCallback.class);
String argument = "foo";
LambdaSafe
.callbacks(NonGenericCallback.class,
Collections.singleton(callbackInstance), argument)
.invoke((c) -> c.handle(argument));
verify(callbackInstance).handle(argument);
}
@Test
@SuppressWarnings("unchecked")
public void callbacksInvokeWhenHasGenericShouldInvokeCallback() {
StringCallback callbackInstance = mock(StringCallback.class);
String argument = "foo";
LambdaSafe.callbacks(GenericCallback.class,
Collections.singleton(callbackInstance), argument)
.invoke((c) -> c.handle(argument));
verify(callbackInstance).handle(argument);
}
@Test
@SuppressWarnings("unchecked")
public void callbacksInvokeWhenHasResolvableGenericMatchShouldInvokeCallback() {
StringBuilderCallback callbackInstance = mock(StringBuilderCallback.class);
StringBuilder argument = new StringBuilder("foo");
LambdaSafe.callbacks(GenericCallback.class,
Collections.singleton(callbackInstance), argument)
.invoke((c) -> c.handle(argument));
verify(callbackInstance).handle(argument);
}
@Test
@SuppressWarnings("unchecked")
public void callbacksInvokeWhenHasResolvableGenericNonMatchShouldNotInvokeCallback() {
GenericCallback<?> callbackInstance = mock(StringBuilderCallback.class);
String argument = "foo";
LambdaSafe.callbacks(GenericCallback.class,
Collections.singleton(callbackInstance), argument)
.invoke((c) -> c.handle(null));
verifyZeroInteractions(callbackInstance);
}
@Test
@SuppressWarnings("unchecked")
public void callbacksInvokeWhenLambdaMismatchShouldSwallowException() {
GenericCallback<StringBuilder> callbackInstance = (s) -> {
fail("Should not get here");
};
String argument = "foo";
LambdaSafe.callbacks(GenericCallback.class,
Collections.singleton(callbackInstance), argument)
.invoke((c) -> c.handle(argument));
}
@Test
@SuppressWarnings("unchecked")
public void callbacksInvokeWhenLambdaMismatchOnDifferentArgumentShouldSwallowException() {
GenericMultiArgCallback<StringBuilder> callbackInstance = (n, s, b) -> {
fail("Should not get here");
};
String argument = "foo";
LambdaSafe
.callbacks(GenericMultiArgCallback.class,
Collections.singleton(callbackInstance), argument)
.invoke((c) -> c.handle(1, argument, false));
}
@Test
public void callbacksInvokeAndWhenNoGenericShouldReturnResult() {
NonGenericFactory callbackInstance = mock(NonGenericFactory.class);
String argument = "foo";
given(callbackInstance.handle("foo")).willReturn(123);
Stream<Integer> result = LambdaSafe
.callbacks(NonGenericFactory.class,
Collections.singleton(callbackInstance), argument)
.invokeAnd((c) -> c.handle(argument));
assertThat(result).containsExactly(123);
}
@Test
@SuppressWarnings("unchecked")
public void callbacksInvokeAndWhenHasGenericShouldReturnResult() {
StringFactory callbackInstance = mock(StringFactory.class);
String argument = "foo";
given(callbackInstance.handle("foo")).willReturn(123);
Stream<Integer> result = LambdaSafe.callbacks(GenericFactory.class,
Collections.singleton(callbackInstance), argument)
.invokeAnd((c) -> c.handle(argument));
assertThat(result).containsExactly(123);
}
@Test
@SuppressWarnings("unchecked")
public void callbacksInvokeAndWhenReturnNullShouldReturnResult() {
StringFactory callbackInstance = mock(StringFactory.class);
String argument = "foo";
given(callbackInstance.handle("foo")).willReturn(null);
Stream<Integer> result = LambdaSafe.callbacks(GenericFactory.class,
Collections.singleton(callbackInstance), argument)
.invokeAnd((c) -> c.handle(argument));
assertThat(result).containsExactly((Integer) null);
}
@Test
@SuppressWarnings("unchecked")
public void callbacksInvokeAndWhenHasResolvableGenericMatchShouldReturnResult() {
StringBuilderFactory callbackInstance = mock(StringBuilderFactory.class);
StringBuilder argument = new StringBuilder("foo");
given(callbackInstance.handle(any(StringBuilder.class))).willReturn(123);
Stream<Integer> result = LambdaSafe.callbacks(GenericFactory.class,
Collections.singleton(callbackInstance), argument)
.invokeAnd((c) -> c.handle(argument));
assertThat(result).containsExactly(123);
}
@Test
@SuppressWarnings("unchecked")
public void callbacksInvokeAndWhenHasResolvableGenericNonMatchShouldReturnNoResult() {
GenericFactory<?> callbackInstance = mock(StringBuilderFactory.class);
String argument = "foo";
Stream<Integer> result = LambdaSafe.callbacks(GenericFactory.class,
Collections.singleton(callbackInstance), argument)
.invokeAnd((c) -> c.handle(argument));
assertThat(result).isEmpty();
}
@Test
@SuppressWarnings("unchecked")
public void callbacksInvokeAndWhenLambdaMismatchShouldSwallowException() {
GenericFactory<StringBuilder> callbackInstance = (s) -> {
fail("Should not get here");
return 123;
};
String argument = "foo";
Stream<Integer> result = LambdaSafe.callbacks(GenericFactory.class,
Collections.singleton(callbackInstance), argument)
.invokeAnd((c) -> (c).handle(argument));
assertThat(result).isEmpty();
}
@Test
@SuppressWarnings("unchecked")
public void callbacksInvokeAndWhenLambdaMismatchOnDifferentArgumentShouldSwallowException() {
GenericMultiArgFactory<StringBuilder> callbackInstance = (n, s, b) -> {
fail("Should not get here");
return 123;
};
String argument = "foo";
Stream<Integer> result = LambdaSafe
.callbacks(GenericMultiArgFactory.class,
Collections.singleton(callbackInstance), argument)
.invokeAnd((c) -> c.handle(1, argument, false));
assertThat(result).isEmpty();
}
@Test
@SuppressWarnings("unchecked")
public void callbacksInvokeWhenMultipleShouldInvokeSuitable() {
List<GenericFactory<?>> callbackInstances = new ArrayList<>();
GenericFactory<String> callback1 = (s) -> 1;
GenericFactory<CharSequence> callback2 = (s) -> 2;
GenericFactory<StringBuilder> callback3 = (s) -> 3;
StringFactory callback4 = mock(StringFactory.class);
given(callback4.handle(any(String.class))).willReturn(4);
StringBuilderFactory callback5 = mock(StringBuilderFactory.class);
given(callback5.handle(any(StringBuilder.class))).willReturn(5);
callbackInstances.add(callback1);
callbackInstances.add(callback2);
callbackInstances.add(callback3);
callbackInstances.add(callback4);
callbackInstances.add(callback5);
String argument = "foo";
Stream<Integer> result = LambdaSafe
.callbacks(GenericFactory.class, callbackInstances, argument)
.invokeAnd((c) -> c.handle(argument));
assertThat(result).containsExactly(1, 2, 4);
}
@Test
@SuppressWarnings("unchecked")
public void callbackWithFilterShouldUseFilter() {
GenericCallback<?> callbackInstance = mock(StringBuilderCallback.class);
String argument = "foo";
LambdaSafe.callback(GenericCallback.class, callbackInstance, argument)
.withFilter(Filter.allowAll()).invoke((c) -> c.handle(null));
verify(callbackInstance).handle(null);
}
@Test
@SuppressWarnings("unchecked")
public void callbackWithLoggerShouldUseLogger() {
Log logger = mock(Log.class);
given(logger.isDebugEnabled()).willReturn(true);
GenericCallback<StringBuilder> callbackInstance = (s) -> {
fail("Should not get here");
};
String argument = "foo";
LambdaSafe.callback(GenericCallback.class, callbackInstance, argument)
.withLogger(logger).invoke((c) -> c.handle(argument));
verify(logger).debug(contains("Non-matching CharSequence type for callback "
+ "LambdaSafeTests.GenericCallback"), any(Throwable.class));
}
interface NonGenericCallback {
void handle(String argument);
}
interface GenericCallback<T extends CharSequence> {
void handle(T argument);
}
interface StringCallback extends GenericCallback<String> {
}
interface StringBuilderCallback extends GenericCallback<StringBuilder> {
}
interface GenericMultiArgCallback<T extends CharSequence> {
void handle(Integer numner, T argument, Boolean bool);
}
interface NonGenericFactory {
Integer handle(String argument);
}
interface GenericFactory<T extends CharSequence> {
Integer handle(T argument);
}
interface StringFactory extends GenericFactory<String> {
}
interface StringBuilderFactory extends GenericFactory<StringBuilder> {
}
interface GenericMultiArgFactory<T extends CharSequence> {
Integer handle(Integer numner, T argument, Boolean bool);
}
}
Loading…
Cancel
Save