ReflectiveMethod.java
/*
* Copyright 2026-present 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.data.jpa.util;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicReference;
import org.jspecify.annotations.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
/**
* A method resolved by name for reflective invocation.
* <p>
* Resolution prefers a public interface declaring the method over the implementation type. Implementation types may
* live in internal packages without being public, so invoking the interface method avoids making internal members
* accessible.
* <p>
* Intended for internal use only.
*
* @author Oscar Fanchin
* @author Christoph Strobl
* @author Mark Paluch
* @since 4.2
*/
public class ReflectiveMethod {
private final Class<?> lookupType;
private final Method method;
private ReflectiveMethod(Class<?> lookupType, Method method) {
this.lookupType = lookupType;
this.method = method;
ReflectionUtils.makeAccessible(method);
}
/**
* Resolve the method named {@code methodName} on {@code type} regardless of its parameters.
*
* @throws IllegalStateException if no such method exists.
*/
public static ReflectiveMethod get(Class<?> type, String methodName) {
ReflectiveMethod method = find(type, methodName, it -> true);
if (method == null) {
throw new IllegalStateException("Cannot resolve %s.%s()".formatted(type.getName(), methodName));
}
return method;
}
/**
* Resolve the method named {@code methodName} on {@code type} accepting exactly {@code parameterTypes}.
*
* @throws IllegalStateException if no such method exists.
*/
public static ReflectiveMethod get(Class<?> type, String methodName, Class<?>... parameterTypes) {
ReflectiveMethod method = find(type, methodName, it -> Arrays.equals(it.getParameterTypes(), parameterTypes));
if (method == null) {
throw new IllegalStateException(
"Cannot resolve %s.%s(%s)".formatted(type.getName(), methodName, Arrays.asList(parameterTypes)));
}
return method;
}
/**
* Resolve the method named {@code methodName} on {@code type} that matches {@code filter}.
*
* @throws IllegalStateException if no such method exists.
* @see #find(Class, String, ReflectionUtils.MethodFilter)
*/
public static ReflectiveMethod get(Class<?> type, String methodName, ReflectionUtils.MethodFilter filter) {
ReflectiveMethod method = find(type, methodName, filter);
if (method == null) {
throw new IllegalStateException("Cannot resolve %s.%s(���)".formatted(type.getName(), methodName));
}
return method;
}
/**
* Find the method named {@code methodName} on {@code type} that matches {@code filter}.
*
* @return the resolved method, or {@literal null} if none matches.
*/
public static @Nullable ReflectiveMethod find(Class<?> type, String methodName, ReflectionUtils.MethodFilter filter) {
Method method = findMethod(type, methodName, filter);
return method != null ? new ReflectiveMethod(type, method) : null;
}
private static @Nullable Method findMethod(Class<?> type, String methodName, ReflectionUtils.MethodFilter filter) {
for (Class<?> candidate : ClassUtils.getAllInterfacesForClass(type)) {
Method method = Modifier.isPublic(candidate.getModifiers()) ? doFindMethod(candidate, methodName, filter) : null;
if (method != null) {
return method;
}
}
return doFindMethod(type, methodName, filter);
}
private static @Nullable Method doFindMethod(Class<?> type, String methodName, ReflectionUtils.MethodFilter filter) {
AtomicReference<Method> ref = new AtomicReference<>();
ReflectionUtils.doWithMethods(type, it -> ref.compareAndSet(null, it),
it -> it.getName().equals(methodName) && filter.matches(it));
return ref.get();
}
/**
* Type on which the method was looked up.
*
* @return the type on which the method was looked up.
*/
public Class<?> getLookupType() {
return lookupType;
}
public String getMethodName() {
return method.getName();
}
public Class<?> getReturnType() {
return method.getReturnType();
}
/**
* Invoke the method on {@code target}, rethrowing target exceptions as
* {@link ReflectionUtils#invokeMethod(Method, Object, Object...)} does.
*
* @param target the receiver, or {@literal null} for a static method.
*/
public @Nullable Object invoke(@Nullable Object target, Object... args) {
return ReflectionUtils.invokeMethod(method, target, args);
}
@Override
public String toString() {
return method.toString();
}
}