org.springframework.boot.util.LambdaSafe Maven / Gradle / Ivy
/*
* Copyright 2012-2023 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 {@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.
* @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 the callback type
* @param the primary argument type
* @return a {@link Callback} instance that can be invoked.
*/
public static Callback callback(Class 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.
* @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 the callback type
* @param the primary argument type
* @return a {@link Callbacks} instance that can be invoked.
*/
public static Callbacks callbacks(Class 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.
*
* @param the callback type
* @param the primary argument type
* @param the self class reference
*/
protected abstract static class LambdaSafeCallback> {
private final Class callbackType;
private final A argument;
private final Object[] additionalArguments;
private Log logger;
private Filter filter = new GenericTypeFilter<>();
LambdaSafeCallback(Class 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 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 filter) {
Assert.notNull(filter, "Filter must not be null");
this.filter = filter;
return self();
}
protected final InvocationResult invoke(C callbackInstance, Supplier 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