Add general utility to deal with lambda callbacks
Add `LambdaSafe` utility that provides a consistent way to deal with the problems that can occur when calling lambda based callbacks. See gh-11584
This commit is contained in:
		
							parent
							
								
									6582afea4a
								
							
						
					
					
						commit
						b0cb728944
					
				| 
						 | 
					@ -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