Skip to content

Commit cfbdafc

Browse files
committed
Introduce ResolvableGenerics type.
Accept simple usages that can be resolved, document resolvable and unresolvable scenarios.
1 parent 7fb57f4 commit cfbdafc

File tree

2 files changed

+303
-22
lines changed

2 files changed

+303
-22
lines changed

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

Lines changed: 202 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,24 +16,32 @@
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;
1922
import java.util.ArrayList;
2023
import java.util.Arrays;
2124
import java.util.Comparator;
25+
import java.util.HashSet;
2226
import java.util.List;
27+
import java.util.Set;
2328
import java.util.function.Consumer;
29+
import java.util.function.Predicate;
2430

2531
import javax.lang.model.element.Modifier;
2632

2733
import org.apache.commons.logging.Log;
2834
import org.apache.commons.logging.LogFactory;
2935
import org.jspecify.annotations.Nullable;
36+
3037
import org.springframework.core.ResolvableType;
3138
import org.springframework.data.projection.ProjectionFactory;
3239
import org.springframework.data.repository.core.RepositoryInformation;
3340
import org.springframework.data.repository.core.support.RepositoryComposition;
3441
import org.springframework.data.repository.core.support.RepositoryFragment;
3542
import org.springframework.data.repository.query.QueryMethod;
3643
import org.springframework.data.util.Lazy;
44+
import org.springframework.data.util.TypeInformation;
3745
import org.springframework.javapoet.ClassName;
3846
import org.springframework.javapoet.FieldSpec;
3947
import org.springframework.javapoet.MethodSpec;
@@ -299,7 +307,7 @@ private void contributeMethod(Method method, @Nullable MethodContributorFactory
299307
return;
300308
}
301309

302-
if (hasUnresolvableGenerics(method)) {
310+
if (ResolvableGenerics.of(method, repositoryInformation.getRepositoryInterface()).hasUnresolvableGenerics()) {
303311

304312
if (logger.isTraceEnabled()) {
305313
logger.trace(
@@ -332,22 +340,208 @@ private void contributeMethod(Method method, @Nullable MethodContributorFactory
332340
generationMetadata.addRepositoryMethod(method, contributor);
333341
}
334342

335-
private boolean hasUnresolvableGenerics(Method method) {
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+
}
336479

337-
if (ResolvableType.forMethodReturnType(method, repositoryInformation.getRepositoryInterface())
338-
.hasUnresolvableGenerics()) {
339480
return true;
340481
}
341482

342-
for (int i = 0; i < method.getParameterCount(); i++) {
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) {
343512

344-
if (ResolvableType.forMethodParameter(method, i, repositoryInformation.getRepositoryInterface())
345-
.hasUnresolvableGenerics()) {
513+
if (predicate.test(resolvableType.getType())) {
346514
return true;
347515
}
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;
348543
}
349544

350-
return false;
351545
}
352546

353547
/**

0 commit comments

Comments
 (0)