Merge branch 'gh-11584'
commit
798882bd3f
@ -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;
|
@ -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…
Reference in New Issue