Browse Source
Make sure to use the raw type or java.lang.Object for unresolvable generic method parameters while retaining the type variable in the method signature. See: #3374pull/3386/head
4 changed files with 341 additions and 257 deletions
@ -0,0 +1,259 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2025-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.repository.aot.generate; |
||||||
|
|
||||||
|
import java.lang.reflect.Method; |
||||||
|
import java.lang.reflect.Parameter; |
||||||
|
import java.lang.reflect.ParameterizedType; |
||||||
|
import java.lang.reflect.Type; |
||||||
|
import java.lang.reflect.TypeVariable; |
||||||
|
import java.lang.reflect.WildcardType; |
||||||
|
import java.util.HashSet; |
||||||
|
import java.util.Set; |
||||||
|
import java.util.function.Predicate; |
||||||
|
|
||||||
|
import org.springframework.core.MethodParameter; |
||||||
|
import org.springframework.core.ResolvableType; |
||||||
|
import org.springframework.data.util.TypeInformation; |
||||||
|
|
||||||
|
/** |
||||||
|
* Value object to determine whether generics in a given {@link Method} can be resolved. Resolvable generics are e.g. |
||||||
|
* declared on the method level (unbounded type variables, type variables using class boundaries). Considers collections |
||||||
|
* and map types. |
||||||
|
* <p> |
||||||
|
* Considers resolvable: |
||||||
|
* <ul> |
||||||
|
* <li>Unbounded method-level type parameters {@code <T> T foo(Class<T>)}</li> |
||||||
|
* <li>Bounded method-level type parameters that resolve to a class
|
||||||
|
* {@code <T extends Serializable> T foo(Class<T>)}</li> |
||||||
|
* <li>Simple references to interface variables {@code T foo(), List<T> foo(…)}</li> |
||||||
|
* <li>Unbounded wildcards {@code User foo(GeoJson<?>)}</li> |
||||||
|
* </ul> |
||||||
|
* Considers non-resolvable: |
||||||
|
* <ul> |
||||||
|
* <li>Parametrized bounds referring to known variables on method-level type parameters |
||||||
|
* {@code <P extends T> T foo(Class<T>), List<? super T> foo()}</li> |
||||||
|
* <li>Generally unresolvable generics</li> |
||||||
|
* </ul> |
||||||
|
* </p> |
||||||
|
* |
||||||
|
* @author Mark Paluch |
||||||
|
*/ |
||||||
|
record ResolvableGenerics(Method method, Class<?> implClass, Set<Type> resolvableTypeVariables, |
||||||
|
Set<Type> unwantedMethodVariables) { |
||||||
|
|
||||||
|
/** |
||||||
|
* Create a new {@code ResolvableGenerics} object for the given {@link Method}. |
||||||
|
* |
||||||
|
* @param method |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
public static ResolvableGenerics of(Method method, Class<?> implClass) { |
||||||
|
return new ResolvableGenerics(method, implClass, getResolvableTypeVariables(method), |
||||||
|
getUnwantedMethodVariables(method)); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Checks if a given {@link Parameter} can be resolved fully. Unresolvable or unwanted type signatures should fall |
||||||
|
* back to using raw types. |
||||||
|
* |
||||||
|
* @param parameter |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
public boolean isFullyResolvableParameter(Parameter parameter) { |
||||||
|
|
||||||
|
MethodParameter methodParameter = MethodParameter.forParameter(parameter).withContainingClass(implClass); |
||||||
|
ResolvableType resolvableType = ResolvableType.forMethodParameter(methodParameter); |
||||||
|
|
||||||
|
return testGenericType(resolvableType, o -> { |
||||||
|
|
||||||
|
if (o instanceof WildcardType wt) { |
||||||
|
return !isResolvable(wt.getLowerBounds()) || !isResolvable(wt.getUpperBounds()); |
||||||
|
} |
||||||
|
|
||||||
|
if (o instanceof ParameterizedType pt) { |
||||||
|
return isResolvable(pt.getActualTypeArguments()); |
||||||
|
} |
||||||
|
|
||||||
|
return unwantedMethodVariables.contains(o); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
private static Set<Type> getResolvableTypeVariables(Method method) { |
||||||
|
|
||||||
|
Set<Type> simpleTypeVariables = new HashSet<>(); |
||||||
|
|
||||||
|
for (TypeVariable<Method> typeParameter : method.getTypeParameters()) { |
||||||
|
if (isClassBounded(typeParameter.getBounds())) { |
||||||
|
simpleTypeVariables.add(typeParameter); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return simpleTypeVariables; |
||||||
|
} |
||||||
|
|
||||||
|
private static Set<Type> getUnwantedMethodVariables(Method method) { |
||||||
|
|
||||||
|
Set<Type> unwanted = new HashSet<>(); |
||||||
|
|
||||||
|
for (TypeVariable<Method> typeParameter : method.getTypeParameters()) { |
||||||
|
if (!isClassBounded(typeParameter.getBounds())) { |
||||||
|
unwanted.add(typeParameter); |
||||||
|
} |
||||||
|
} |
||||||
|
return unwanted; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Check whether the {@link Method} has unresolvable generics when being considered in the context of the |
||||||
|
* implementation class. |
||||||
|
* |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
public boolean hasUnresolvableGenerics() { |
||||||
|
|
||||||
|
ResolvableType resolvableType = ResolvableType.forMethodReturnType(method, implClass); |
||||||
|
|
||||||
|
if (isUnresolvable(resolvableType)) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
for (int i = 0; i < method.getParameterCount(); i++) { |
||||||
|
if (isUnresolvable(ResolvableType.forMethodParameter(method, i, implClass))) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
private boolean isUnresolvable(TypeInformation<?> typeInformation) { |
||||||
|
return isUnresolvable(typeInformation.toResolvableType()); |
||||||
|
} |
||||||
|
|
||||||
|
private boolean isUnresolvable(ResolvableType resolvableType) { |
||||||
|
|
||||||
|
if (isResolvable(resolvableType)) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
if (isUnwanted(resolvableType)) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
if (resolvableType.isAssignableFrom(Class.class)) { |
||||||
|
return isUnresolvable(resolvableType.getGeneric(0)); |
||||||
|
} |
||||||
|
|
||||||
|
TypeInformation<?> typeInformation = TypeInformation.of(resolvableType); |
||||||
|
if (typeInformation.isMap() || typeInformation.isCollectionLike()) { |
||||||
|
|
||||||
|
for (ResolvableType type : resolvableType.getGenerics()) { |
||||||
|
if (isUnresolvable(type)) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
if (typeInformation.getActualType() != null && typeInformation.getActualType() != typeInformation) { |
||||||
|
return isUnresolvable(typeInformation.getRequiredActualType()); |
||||||
|
} |
||||||
|
|
||||||
|
return resolvableType.hasUnresolvableGenerics(); |
||||||
|
} |
||||||
|
|
||||||
|
private boolean isResolvable(Type[] types) { |
||||||
|
|
||||||
|
for (Type type : types) { |
||||||
|
|
||||||
|
if (resolvableTypeVariables.contains(type)) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
if (isClass(type)) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
private boolean isResolvable(ResolvableType resolvableType) { |
||||||
|
|
||||||
|
return testGenericType(resolvableType, it -> { |
||||||
|
|
||||||
|
if (resolvableTypeVariables.contains(it)) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
if (it instanceof WildcardType wt) { |
||||||
|
return isClassBounded(wt.getLowerBounds()) && isClassBounded(wt.getUpperBounds()); |
||||||
|
} |
||||||
|
|
||||||
|
return false; |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
private boolean isUnwanted(ResolvableType resolvableType) { |
||||||
|
|
||||||
|
return testGenericType(resolvableType, o -> { |
||||||
|
|
||||||
|
if (o instanceof WildcardType wt) { |
||||||
|
return !isResolvable(wt.getLowerBounds()) || !isResolvable(wt.getUpperBounds()); |
||||||
|
} |
||||||
|
|
||||||
|
return unwantedMethodVariables.contains(o); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
private static boolean testGenericType(ResolvableType resolvableType, Predicate<Type> predicate) { |
||||||
|
|
||||||
|
if (predicate.test(resolvableType.getType())) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
ResolvableType[] generics = resolvableType.getGenerics(); |
||||||
|
for (ResolvableType generic : generics) { |
||||||
|
if (testGenericType(generic, predicate)) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
private static boolean isClassBounded(Type[] bounds) { |
||||||
|
|
||||||
|
for (Type bound : bounds) { |
||||||
|
|
||||||
|
if (isClass(bound)) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
private static boolean isClass(Type type) { |
||||||
|
return type instanceof Class; |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue