Support WildcardType resolution in GenericTypeResolver
This commit adds support for WildcardType bounds resolution, commonly seen in Kotlin due to declaration-site variance, but also possible in Java even if less common. Closes gh-22313
This commit is contained in:
parent
b8f091e2f6
commit
f075120675
|
|
@ -39,6 +39,7 @@ import org.springframework.util.ConcurrentReferenceHashMap;
|
||||||
* @author Rob Harrop
|
* @author Rob Harrop
|
||||||
* @author Sam Brannen
|
* @author Sam Brannen
|
||||||
* @author Phillip Webb
|
* @author Phillip Webb
|
||||||
|
* @author Sebastien Deleuze
|
||||||
* @since 2.5.2
|
* @since 2.5.2
|
||||||
*/
|
*/
|
||||||
public final class GenericTypeResolver {
|
public final class GenericTypeResolver {
|
||||||
|
|
@ -166,7 +167,6 @@ public final class GenericTypeResolver {
|
||||||
}
|
}
|
||||||
else if (genericType instanceof ParameterizedType parameterizedType) {
|
else if (genericType instanceof ParameterizedType parameterizedType) {
|
||||||
ResolvableType resolvedType = ResolvableType.forType(genericType);
|
ResolvableType resolvedType = ResolvableType.forType(genericType);
|
||||||
if (resolvedType.hasUnresolvableGenerics()) {
|
|
||||||
Class<?>[] generics = new Class<?>[parameterizedType.getActualTypeArguments().length];
|
Class<?>[] generics = new Class<?>[parameterizedType.getActualTypeArguments().length];
|
||||||
Type[] typeArguments = parameterizedType.getActualTypeArguments();
|
Type[] typeArguments = parameterizedType.getActualTypeArguments();
|
||||||
ResolvableType contextType = ResolvableType.forClass(contextClass);
|
ResolvableType contextType = ResolvableType.forClass(contextClass);
|
||||||
|
|
@ -181,6 +181,9 @@ public final class GenericTypeResolver {
|
||||||
generics[i] = ResolvableType.forType(typeArgument).resolve();
|
generics[i] = ResolvableType.forType(typeArgument).resolve();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (typeArgument instanceof WildcardType wildcardType) {
|
||||||
|
generics[i] = resolveWildcard(wildcardType, contextType).resolve();
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
generics[i] = ResolvableType.forType(typeArgument).resolve();
|
generics[i] = ResolvableType.forType(typeArgument).resolve();
|
||||||
}
|
}
|
||||||
|
|
@ -191,7 +194,6 @@ public final class GenericTypeResolver {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return genericType;
|
return genericType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -224,6 +226,26 @@ public final class GenericTypeResolver {
|
||||||
return ResolvableType.NONE;
|
return ResolvableType.NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static ResolvableType resolveWildcard(WildcardType wildcardType, ResolvableType contextType) {
|
||||||
|
for (Type bound : wildcardType.getUpperBounds()) {
|
||||||
|
if (bound instanceof TypeVariable<?> typeVariable) {
|
||||||
|
ResolvableType resolvedTypeArgument = resolveVariable(typeVariable, contextType);
|
||||||
|
if (resolvedTypeArgument != ResolvableType.NONE) {
|
||||||
|
return resolvedTypeArgument;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (Type bound : wildcardType.getLowerBounds()) {
|
||||||
|
if (bound instanceof TypeVariable<?> typeVariable) {
|
||||||
|
ResolvableType resolvedTypeArgument = resolveVariable(typeVariable, contextType);
|
||||||
|
if (resolvedTypeArgument != ResolvableType.NONE) {
|
||||||
|
return resolvedTypeArgument;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ResolvableType.forType(wildcardType);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve the specified generic type against the given TypeVariable map.
|
* Resolve the specified generic type against the given TypeVariable map.
|
||||||
* <p>Used by Spring Data.
|
* <p>Used by Spring Data.
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2022 the original author or authors.
|
* Copyright 2002-2023 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -37,6 +37,7 @@ import static org.springframework.util.ReflectionUtils.findMethod;
|
||||||
/**
|
/**
|
||||||
* @author Juergen Hoeller
|
* @author Juergen Hoeller
|
||||||
* @author Sam Brannen
|
* @author Sam Brannen
|
||||||
|
* @author Sebastien Deleuze
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||||
class GenericTypeResolverTests {
|
class GenericTypeResolverTests {
|
||||||
|
|
@ -185,6 +186,51 @@ class GenericTypeResolverTests {
|
||||||
assertThat(resolved).isEqualTo(E.class);
|
assertThat(resolved).isEqualTo(E.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void resolveWildcardTypeWithUpperBound() {
|
||||||
|
Method method = findMethod(MySimpleSuperclassType.class, "upperBound", List.class);
|
||||||
|
Type resolved = resolveType(method.getGenericParameterTypes()[0], MySimpleSuperclassType.class);
|
||||||
|
ResolvableType resolvableType = ResolvableType.forType(resolved);
|
||||||
|
assertThat(resolvableType.hasUnresolvableGenerics()).isFalse();
|
||||||
|
assertThat(resolvableType.resolveGenerics()).containsExactly(String.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void resolveWildcardTypeWithUpperBoundWithResolvedType() {
|
||||||
|
Method method = findMethod(MySimpleSuperclassType.class, "upperBoundWithResolvedType", List.class);
|
||||||
|
Type resolved = resolveType(method.getGenericParameterTypes()[0], MySimpleSuperclassType.class);
|
||||||
|
ResolvableType resolvableType = ResolvableType.forType(resolved);
|
||||||
|
assertThat(resolvableType.hasUnresolvableGenerics()).isFalse();
|
||||||
|
assertThat(resolvableType.resolveGenerics()).containsExactly(Integer.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void resolveWildcardTypeWithLowerBound() {
|
||||||
|
Method method = findMethod(MySimpleSuperclassType.class, "lowerBound", List.class);
|
||||||
|
Type resolved = resolveType(method.getGenericParameterTypes()[0], MySimpleSuperclassType.class);
|
||||||
|
ResolvableType resolvableType = ResolvableType.forType(resolved);
|
||||||
|
assertThat(resolvableType.hasUnresolvableGenerics()).isFalse();
|
||||||
|
assertThat(resolvableType.resolveGenerics()).containsExactly(String.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void resolveWildcardTypeWithLowerBoundWithResolvedType() {
|
||||||
|
Method method = findMethod(MySimpleSuperclassType.class, "lowerBoundWithResolvedType", List.class);
|
||||||
|
Type resolved = resolveType(method.getGenericParameterTypes()[0], MySimpleSuperclassType.class);
|
||||||
|
ResolvableType resolvableType = ResolvableType.forType(resolved);
|
||||||
|
assertThat(resolvableType.hasUnresolvableGenerics()).isFalse();
|
||||||
|
assertThat(resolvableType.resolveGenerics()).containsExactly(Integer.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void resolveWildcardTypeWithUnbounded() {
|
||||||
|
Method method = findMethod(MySimpleSuperclassType.class, "unbounded", List.class);
|
||||||
|
Type resolved = resolveType(method.getGenericParameterTypes()[0], MySimpleSuperclassType.class);
|
||||||
|
ResolvableType resolvableType = ResolvableType.forType(resolved);
|
||||||
|
assertThat(resolvableType.hasUnresolvableGenerics()).isFalse();
|
||||||
|
assertThat(resolvableType.resolveGenerics()).containsExactly(Object.class);
|
||||||
|
}
|
||||||
|
|
||||||
public interface MyInterfaceType<T> {
|
public interface MyInterfaceType<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -195,6 +241,21 @@ class GenericTypeResolverTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract class MySuperclassType<T> {
|
public abstract class MySuperclassType<T> {
|
||||||
|
|
||||||
|
public void upperBound(List<? extends T> list) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void upperBoundWithResolvedType(List<? extends Integer> list) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void lowerBound(List<? extends T> list) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void lowerBoundWithResolvedType(List<? super Integer> list) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unbounded(List<?> list) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class MySimpleSuperclassType extends MySuperclassType<String> {
|
public class MySimpleSuperclassType extends MySuperclassType<String> {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue