Skip to content

Commit c2a135a

Browse files
Erase MethodParameter generic type signature when unresolvable.
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: #3374
1 parent 70d1332 commit c2a135a

File tree

4 files changed

+341
-257
lines changed

4 files changed

+341
-257
lines changed

src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryCreator.java

Lines changed: 0 additions & 213 deletions
Original file line numberDiff line numberDiff line change
@@ -16,32 +16,23 @@
1616
package org.springframework.data.repository.aot.generate;
1717

1818
import java.lang.reflect.Method;
19-
import java.lang.reflect.Type;
20-
import java.lang.reflect.TypeVariable;
21-
import java.lang.reflect.WildcardType;
2219
import java.util.ArrayList;
2320
import java.util.Arrays;
2421
import java.util.Comparator;
25-
import java.util.HashSet;
2622
import java.util.List;
27-
import java.util.Set;
2823
import java.util.function.Consumer;
29-
import java.util.function.Predicate;
3024

3125
import javax.lang.model.element.Modifier;
3226

3327
import org.apache.commons.logging.Log;
3428
import org.apache.commons.logging.LogFactory;
3529
import org.jspecify.annotations.Nullable;
36-
37-
import org.springframework.core.ResolvableType;
3830
import org.springframework.data.projection.ProjectionFactory;
3931
import org.springframework.data.repository.core.RepositoryInformation;
4032
import org.springframework.data.repository.core.support.RepositoryComposition;
4133
import org.springframework.data.repository.core.support.RepositoryFragment;
4234
import org.springframework.data.repository.query.QueryMethod;
4335
import org.springframework.data.util.Lazy;
44-
import org.springframework.data.util.TypeInformation;
4536
import org.springframework.javapoet.ClassName;
4637
import org.springframework.javapoet.FieldSpec;
4738
import org.springframework.javapoet.MethodSpec;
@@ -340,210 +331,6 @@ private void contributeMethod(Method method, @Nullable MethodContributorFactory
340331
generationMetadata.addRepositoryMethod(method, contributor);
341332
}
342333

343-
/**
344-
* Value object to determine whether generics in a given {@link Method} can be resolved. Resolvable generics are e.g.
345-
* declared on the method level (unbounded type variables, type variables using class boundaries). Considers
346-
* collections and map types.
347-
* <p>
348-
* Considers resolvable:
349-
* <ul>
350-
* <li>Unbounded method-level type parameters {@code <T> T foo(Class<T>)}</li>
351-
* <li>Bounded method-level type parameters that resolve to a class
352-
* {@code <T extends Serializable> T foo(Class<T>)}</li>
353-
* <li>Simple references to interface variables {@code T foo(), List<T> foo(…)}</li>
354-
* <li>Unbounded wildcards {@code User foo(GeoJson<?>)}</li>
355-
* </ul>
356-
* Considers non-resolvable:
357-
* <ul>
358-
* <li>Parametrized bounds referring to known variables on method-level type parameters
359-
* {@code <P extends T> T foo(Class<T>), List<? super T> foo()}</li>
360-
* <li>Generally unresolvable generics</li>
361-
* </ul>
362-
* </p>
363-
*
364-
* @author Mark Paluch
365-
*/
366-
record ResolvableGenerics(Method method, Class<?> implClass, Set<Type> resolvableTypeVariables,
367-
Set<Type> unwantedMethodVariables) {
368-
369-
/**
370-
* Create a new {@code ResolvableGenerics} object for the given {@link Method}.
371-
*
372-
* @param method
373-
* @return
374-
*/
375-
public static ResolvableGenerics of(Method method, Class<?> implClass) {
376-
return new ResolvableGenerics(method, implClass, getResolvableTypeVariables(method),
377-
getUnwantedMethodVariables(method));
378-
}
379-
380-
private static Set<Type> getResolvableTypeVariables(Method method) {
381-
382-
Set<Type> simpleTypeVariables = new HashSet<>();
383-
384-
for (TypeVariable<Method> typeParameter : method.getTypeParameters()) {
385-
if (isClassBounded(typeParameter.getBounds())) {
386-
simpleTypeVariables.add(typeParameter);
387-
}
388-
}
389-
390-
return simpleTypeVariables;
391-
}
392-
393-
private static Set<Type> getUnwantedMethodVariables(Method method) {
394-
395-
Set<Type> unwanted = new HashSet<>();
396-
397-
for (TypeVariable<Method> typeParameter : method.getTypeParameters()) {
398-
if (!isClassBounded(typeParameter.getBounds())) {
399-
unwanted.add(typeParameter);
400-
}
401-
}
402-
return unwanted;
403-
}
404-
405-
/**
406-
* Check whether the {@link Method} has unresolvable generics when being considered in the context of the
407-
* implementation class.
408-
*
409-
* @return
410-
*/
411-
public boolean hasUnresolvableGenerics() {
412-
413-
ResolvableType resolvableType = ResolvableType.forMethodReturnType(method, implClass);
414-
415-
if (isUnresolvable(resolvableType)) {
416-
return true;
417-
}
418-
419-
for (int i = 0; i < method.getParameterCount(); i++) {
420-
if (isUnresolvable(ResolvableType.forMethodParameter(method, i, implClass))) {
421-
return true;
422-
}
423-
}
424-
425-
return false;
426-
}
427-
428-
private boolean isUnresolvable(TypeInformation<?> typeInformation) {
429-
return isUnresolvable(typeInformation.toResolvableType());
430-
}
431-
432-
private boolean isUnresolvable(ResolvableType resolvableType) {
433-
434-
if (isResolvable(resolvableType)) {
435-
return false;
436-
}
437-
438-
if (isUnwanted(resolvableType)) {
439-
return true;
440-
}
441-
442-
if (resolvableType.isAssignableFrom(Class.class)) {
443-
return isUnresolvable(resolvableType.getGeneric(0));
444-
}
445-
446-
TypeInformation<?> typeInformation = TypeInformation.of(resolvableType);
447-
if (typeInformation.isMap() || typeInformation.isCollectionLike()) {
448-
449-
for (ResolvableType type : resolvableType.getGenerics()) {
450-
if (isUnresolvable(type)) {
451-
return true;
452-
}
453-
}
454-
455-
return false;
456-
}
457-
458-
if (typeInformation.getActualType() != null && typeInformation.getActualType() != typeInformation) {
459-
return isUnresolvable(typeInformation.getRequiredActualType());
460-
}
461-
462-
return resolvableType.hasUnresolvableGenerics();
463-
}
464-
465-
private boolean isResolvable(Type[] types) {
466-
467-
for (Type type : types) {
468-
469-
if (resolvableTypeVariables.contains(type)) {
470-
continue;
471-
}
472-
473-
if (isClass(type)) {
474-
continue;
475-
}
476-
477-
return false;
478-
}
479-
480-
return true;
481-
}
482-
483-
private boolean isResolvable(ResolvableType resolvableType) {
484-
485-
return testGenericType(resolvableType, it -> {
486-
487-
if (resolvableTypeVariables.contains(it)) {
488-
return true;
489-
}
490-
491-
if (it instanceof WildcardType wt) {
492-
return isClassBounded(wt.getLowerBounds()) && isClassBounded(wt.getUpperBounds());
493-
}
494-
495-
return false;
496-
});
497-
}
498-
499-
private boolean isUnwanted(ResolvableType resolvableType) {
500-
501-
return testGenericType(resolvableType, o -> {
502-
503-
if (o instanceof WildcardType wt) {
504-
return !isResolvable(wt.getLowerBounds()) || !isResolvable(wt.getUpperBounds());
505-
}
506-
507-
return unwantedMethodVariables.contains(o);
508-
});
509-
}
510-
511-
private static boolean testGenericType(ResolvableType resolvableType, Predicate<Type> predicate) {
512-
513-
if (predicate.test(resolvableType.getType())) {
514-
return true;
515-
}
516-
517-
ResolvableType[] generics = resolvableType.getGenerics();
518-
for (ResolvableType generic : generics) {
519-
if (testGenericType(generic, predicate)) {
520-
return true;
521-
}
522-
}
523-
524-
return false;
525-
}
526-
527-
private static boolean isClassBounded(Type[] bounds) {
528-
529-
for (Type bound : bounds) {
530-
531-
if (isClass(bound)) {
532-
continue;
533-
}
534-
535-
return false;
536-
}
537-
538-
return true;
539-
}
540-
541-
private static boolean isClass(Type type) {
542-
return type instanceof Class;
543-
}
544-
545-
}
546-
547334
/**
548335
* Factory interface to conditionally create {@link MethodContributor} instances. An implementation may decide whether
549336
* to return a {@link MethodContributor} or {@literal null}, if no method (code or metadata) should be contributed.

src/main/java/org/springframework/data/repository/aot/generate/MethodMetadata.java

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
import java.util.function.Function;
2727

2828
import org.jspecify.annotations.Nullable;
29-
3029
import org.springframework.core.DefaultParameterNameDiscoverer;
3130
import org.springframework.core.MethodParameter;
3231
import org.springframework.core.ParameterNameDiscoverer;
@@ -36,6 +35,7 @@
3635
import org.springframework.data.util.TypeInformation;
3736
import org.springframework.javapoet.ParameterSpec;
3837
import org.springframework.javapoet.TypeName;
38+
import org.springframework.util.Assert;
3939
import org.springframework.util.ClassUtils;
4040

4141
/**
@@ -79,14 +79,17 @@ private static void initializeMethodArguments(Method method, ParameterNameDiscov
7979
ResolvableType repositoryInterface, Map<String, ParameterSpec> methodArguments,
8080
Map<String, MethodParameter> methodParameters) {
8181

82+
Class<?> repositoryInterfaceType = repositoryInterface.toClass();
83+
8284
for (Parameter parameter : method.getParameters()) {
8385

84-
MethodParameter methodParameter = MethodParameter.forParameter(parameter).withContainingClass(repositoryInterface.resolve());
86+
MethodParameter methodParameter = MethodParameter.forParameter(parameter)
87+
.withContainingClass(repositoryInterfaceType);
8588
methodParameter.initParameterNameDiscovery(nameDiscoverer);
86-
ResolvableType resolvableParameterType = ResolvableType.forMethodParameter(methodParameter);
8789

88-
TypeName parameterType = TypeNames.resolvedTypeName(resolvableParameterType);
90+
TypeName parameterType = parameterTypeName(methodParameter, repositoryInterfaceType);
8991

92+
Assert.notNull(methodParameter.getParameterName(), "MethodParameter.getParameterName() must not be null");
9093
ParameterSpec parameterSpec = ParameterSpec.builder(parameterType, methodParameter.getParameterName()).build();
9194

9295
if (methodArguments.containsKey(parameterSpec.name())) {
@@ -98,6 +101,20 @@ private static void initializeMethodArguments(Method method, ParameterNameDiscov
98101
}
99102
}
100103

104+
@SuppressWarnings("NullAway")
105+
static TypeName parameterTypeName(MethodParameter methodParameter, Class<?> repositoryInterface) {
106+
107+
ResolvableType resolvableParameterType = ResolvableType.forMethodParameter(methodParameter);
108+
if (ClassUtils.isPrimitiveOrWrapper(resolvableParameterType.toClass())) {
109+
return TypeNames.className(resolvableParameterType);
110+
}
111+
112+
ResolvableGenerics resolvableGenerics = ResolvableGenerics.of(methodParameter.getMethod(), repositoryInterface);
113+
return resolvableGenerics.isFullyResolvableParameter(methodParameter.getParameter())
114+
? TypeNames.resolvedTypeName(resolvableParameterType)
115+
: TypeNames.className(resolvableParameterType);
116+
}
117+
101118
ResolvableType getReturnType() {
102119
return returnType;
103120
}

0 commit comments

Comments
 (0)