/*
 * Copyright 2012-2019 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
 *
 *      https://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.lang.reflect.Method;
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;
import org.springframework.util.ReflectionUtils;

Utility that can be used to invoke lambdas in a safe way. Primarily designed to help support generically typed callbacks where class cast exceptions need to be dealt with due to class erasure.
Author:Phillip Webb
Since:2.0.0
/** * 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 static final Method CLASS_GET_MODULE; private static final Method MODULE_GET_NAME; static { CLASS_GET_MODULE = ReflectionUtils.findMethod(Class.class, "getModule"); MODULE_GET_NAME = (CLASS_GET_MODULE != null) ? ReflectionUtils.findMethod(CLASS_GET_MODULE.getReturnType(), "getName") : null; } private LambdaSafe() { }
Start a call to a single callback instance, dealing with common generic type concerns and exceptions.
Params:
  • callbackType – the callback type (a functional interface)
  • callbackInstance – the callback instance (may be a lambda)
  • argument – the primary argument passed to the callback
  • additionalArguments – any additional arguments passed to the callback
Type parameters:
  • <C> – the callback type
  • <A> – the primary argument type
Returns:a Callback instance that can be invoked.
/** * 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 arguments 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 callback instances, dealing with common generic type concerns and exceptions.
Params:
  • callbackType – the callback type (a functional interface)
  • callbackInstances – the callback instances (elements may be lambdas)
  • argument – the primary argument passed to the callbacks
  • additionalArguments – any additional arguments passed to the callbacks
Type parameters:
  • <C> – the callback type
  • <A> – the primary argument type
Returns:a Callbacks instance that can be invoked.
/** * Start a call to callback instances, 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 arguments 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<>(callbackType, callbackInstances, argument, additionalArguments); }
Abstract base class for lambda safe callbacks.
Type parameters:
  • <C> – the callback type
  • <A> – the primary argument type
  • <SELF> – the self class reference
/** * Abstract base class for lambda safe callbacks. * * @param <C> the callback type * @param <A> the primary argument type * @param <SELF> the self class reference */
protected abstract static 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<>(); 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.
Params:
  • loggerSource – the logger source to use
Returns:this instance
/** * 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.
Params:
  • logger – the logger to use
Returns:this instance
/** * 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 no explicit filter is set filter will be attempted using the generic type on the callback type.
Params:
  • filter – the filter to use
Returns:this instance
/** * Use a specific filter to determine when a callback should apply. If no 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 */
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; } logNonMatchingType(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) -> startsWithArgumentClassName(message, argument); return startsWith.test(this.argument) || Stream.of(this.additionalArguments).anyMatch(startsWith); } private boolean startsWithArgumentClassName(String message, Object argument) { if (argument == null) { return false; } Class<?> argumentType = argument.getClass(); // On Java 8, the message starts with the class name: "java.lang.String cannot // be cast..." if (message.startsWith(argumentType.getName())) { return true; } // On Java 11, the message starts with "class ..." a.k.a. Class.toString() if (message.startsWith(argumentType.toString())) { return true; } // On Java 9, the message used to contain the module name: // "java.base/java.lang.String cannot be cast..." int moduleSeparatorIndex = message.indexOf('/'); if (moduleSeparatorIndex != -1 && message.startsWith(argumentType.getName(), moduleSeparatorIndex + 1)) { return true; } if (CLASS_GET_MODULE != null) { Object module = ReflectionUtils.invokeMethod(CLASS_GET_MODULE, argumentType); Object moduleName = ReflectionUtils.invokeMethod(MODULE_GET_NAME, module); return message.startsWith(moduleName + "/" + argumentType.getName()); } return false; } private void logNonMatchingType(C callback, ClassCastException ex) { if (this.logger.isDebugEnabled()) { Class<?> expectedType = ResolvableType.forClass(this.callbackType).resolveGeneric(); String expectedTypeName = (expectedType != null) ? ClassUtils.getShortName(expectedType) + " type" : "type"; String message = "Non-matching " + expectedTypeName + " 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.
Type parameters:
  • <C> – the callback type
  • <A> – the primary argument type
/** * 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 final 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.
Params:
  • invoker – the invoker used to invoke the callback
/** * 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.
Params:
  • invoker – the invoker used to invoke the callback
Type parameters:
  • <R> – the result type
Returns:the result of the invocation (may be InvocationResult.noResult if the callback was not invoked)
/** * 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.
Type parameters:
  • <C> – the callback type
  • <A> – the primary argument type
/** * 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 final 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.
Params:
  • invoker – the invoker used to invoke the callback
/** * 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) { this.callbackInstances.forEach((callbackInstance) -> invoke(callbackInstance, () -> { invoker.accept(callbackInstance); return null; })); }
Invoke the callback instances where the callback method returns a result.
Params:
  • invoker – the invoker used to invoke the callback
Type parameters:
  • <R> – the result type
Returns:the results of the invocation (may be an empty stream if no callbacks could be called)
/** * 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 no 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.
Type parameters:
  • <C> – the callback type
  • <A> – the primary argument type
/** * 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.
Params:
  • callbackType – the callback type (the functional interface)
  • callbackInstance – the callback instance (the implementation)
  • argument – the primary argument
  • additionalArguments – any additional arguments
Returns:if the callback matches and should be invoked
/** * 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 Filter that allows all callbacks to be invoked.
Type parameters:
  • <C> – the callback type
  • <A> – the primary argument type
Returns:an "allow all" filter
/** * 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; } }
Filter that matches when the callback has a single generic and primary argument is an instance of it.
/** * {@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, null or absent entirely if the callback wasn't suitable. Similar in design to Optional but allows for null as a valid value.
Type parameters:
  • <R> – the result type
/** * 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 static final class InvocationResult<R> { private static final InvocationResult<?> NONE = new InvocationResult<>(null); private final R value; private InvocationResult(R value) { this.value = value; }
Return true if a result in present.
Returns:if a result is present
/** * 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 null if the callback wasn't suitable.
Returns:the result of the invocation or null
/** * 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.
Params:
  • fallback – the fallback to use when there is no result
Returns:the result of the invocation or the fallback
/** * 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) ? this.value : fallback; }
Create a new InvocationResult instance with the specified value.
Params:
  • value – the value (may be null)
Type parameters:
  • <R> – the result type
Returns:an InvocationResult
/** * 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<>(value); }
Return an InvocationResult instance representing no result.
Type parameters:
  • <R> – the result type
Returns:an InvocationResult
/** * 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; } } }