diff --git a/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java b/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java index 27f8682f474b..b3bd606aa4e0 100644 --- a/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java +++ b/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java @@ -39,6 +39,7 @@ * @author Rob Harrop * @author Sam Brannen * @author Phillip Webb + * @author Sebastien Deleuze * @since 2.5.2 */ public final class GenericTypeResolver { @@ -166,29 +167,30 @@ public static Type resolveType(Type genericType, @Nullable Class contextClass } else if (genericType instanceof ParameterizedType parameterizedType) { ResolvableType resolvedType = ResolvableType.forType(genericType); - if (resolvedType.hasUnresolvableGenerics()) { - Class[] generics = new Class[parameterizedType.getActualTypeArguments().length]; - Type[] typeArguments = parameterizedType.getActualTypeArguments(); - ResolvableType contextType = ResolvableType.forClass(contextClass); - for (int i = 0; i < typeArguments.length; i++) { - Type typeArgument = typeArguments[i]; - if (typeArgument instanceof TypeVariable typeVariable) { - ResolvableType resolvedTypeArgument = resolveVariable(typeVariable, contextType); - if (resolvedTypeArgument != ResolvableType.NONE) { - generics[i] = resolvedTypeArgument.resolve(); - } - else { - generics[i] = ResolvableType.forType(typeArgument).resolve(); - } + Class[] generics = new Class[parameterizedType.getActualTypeArguments().length]; + Type[] typeArguments = parameterizedType.getActualTypeArguments(); + ResolvableType contextType = ResolvableType.forClass(contextClass); + for (int i = 0; i < typeArguments.length; i++) { + Type typeArgument = typeArguments[i]; + if (typeArgument instanceof TypeVariable typeVariable) { + ResolvableType resolvedTypeArgument = resolveVariable(typeVariable, contextType); + if (resolvedTypeArgument != ResolvableType.NONE) { + generics[i] = resolvedTypeArgument.resolve(); } else { generics[i] = ResolvableType.forType(typeArgument).resolve(); } } - Class rawClass = resolvedType.getRawClass(); - if (rawClass != null) { - return ResolvableType.forClassWithGenerics(rawClass, generics).getType(); + else if (typeArgument instanceof WildcardType wildcardType) { + generics[i] = resolveWildcard(wildcardType, contextType).resolve(); } + else { + generics[i] = ResolvableType.forType(typeArgument).resolve(); + } + } + Class rawClass = resolvedType.getRawClass(); + if (rawClass != null) { + return ResolvableType.forClassWithGenerics(rawClass, generics).getType(); } } } @@ -224,6 +226,26 @@ private static ResolvableType resolveVariable(TypeVariable typeVariable, Reso 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. *

Used by Spring Data. diff --git a/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java b/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java index 46e19190f4f5..5995c9f669a5 100644 --- a/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java +++ b/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java @@ -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"); * you may not use this file except in compliance with the License. @@ -37,6 +37,7 @@ /** * @author Juergen Hoeller * @author Sam Brannen + * @author Sebastien Deleuze */ @SuppressWarnings({"unchecked", "rawtypes"}) class GenericTypeResolverTests { @@ -185,6 +186,51 @@ public void resolveTransitiveTypeVariableWithDifferentName() { 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 { } @@ -195,6 +241,21 @@ public class MyCollectionInterfaceType implements MyInterfaceType { + + public void upperBound(List list) { + } + + public void upperBoundWithResolvedType(List list) { + } + + public void lowerBound(List list) { + } + + public void lowerBoundWithResolvedType(List list) { + } + + public void unbounded(List list) { + } } public class MySimpleSuperclassType extends MySuperclassType {