From 18a8995e2aed3c711b23842eea2546db56fbce83 Mon Sep 17 00:00:00 2001 From: anaconda875 Date: Mon, 16 Mar 2026 14:54:04 +0700 Subject: [PATCH 1/3] Add test cases Signed-off-by: anaconda875 --- .../support/RestClientAdapterTests.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/spring-web/src/test/java/org/springframework/web/client/support/RestClientAdapterTests.java b/spring-web/src/test/java/org/springframework/web/client/support/RestClientAdapterTests.java index 95a8d6b8ec4f..0ec36e5cb429 100644 --- a/spring-web/src/test/java/org/springframework/web/client/support/RestClientAdapterTests.java +++ b/spring-web/src/test/java/org/springframework/web/client/support/RestClientAdapterTests.java @@ -25,6 +25,7 @@ import java.net.URI; import java.nio.charset.StandardCharsets; import java.util.LinkedHashSet; +import java.util.List; import java.util.Optional; import java.util.Set; import java.util.function.BiFunction; @@ -103,6 +104,12 @@ void shutdown() { @interface ParameterizedAdapterTest { } + public static Stream wildcardCases() { + return Stream.of( + (Function>) BaseClient::getListWildcardUpperBound1, + (Function>) BaseClient::getListWildcardUpperBound2); + } + public static Stream arguments() throws IOException { return Stream.of( createArgsForAdapter((url, or) -> { @@ -216,6 +223,15 @@ void getEntityWithGenericReturnType() { assertThat(entity.getBody().name()).isEqualTo("Karl"); } + @ParameterizedTest + @MethodSource("wildcardCases") + void getWildcardReturnType(Function> invocation) { + PersonClient client = initService(PersonClient.class); + prepareResponse(r -> r.setHeader("Content-Type", "application/json").body("[{\"name\":\"Karl\"}]")); + List list = invocation.apply(client); + assertThat(list.get(0).name()).isEqualTo("Karl"); + } + @ParameterizedAdapterTest void getWithUriBuilderFactory(MockWebServer server, Service service) throws InterruptedException { prepareResponse(builder -> @@ -467,6 +483,12 @@ private interface BaseClient { @GetExchange T getBody(); + @GetExchange + List getListWildcardUpperBound1(); + + @GetExchange + List getListWildcardUpperBound2(); + @GetExchange ResponseEntity getEntity(); } From 1e4f2eac44adefb2c9ca9dee23d8102c75c17da0 Mon Sep 17 00:00:00 2001 From: anaconda875 Date: Mon, 16 Mar 2026 15:41:30 +0700 Subject: [PATCH 2/3] Fix generic with WildcardType return type support in HttpServiceMethod Signed-off-by: anaconda875 --- .../core/GenericTypeResolver.java | 42 ++++-- .../springframework/core/ResolvableType.java | 124 +++++++++++++++++- .../core/GenericTypeResolverTests.java | 28 ++++ .../core/ResolvableTypeTests.java | 32 ++++- 4 files changed, 211 insertions(+), 15 deletions(-) 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 083964732733..1cd981de6c38 100644 --- a/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java +++ b/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java @@ -154,11 +154,8 @@ public static Class resolveReturnType(Method method, Class clazz) { public static Type resolveType(Type genericType, @Nullable Class contextClass) { if (contextClass != null) { if (genericType instanceof TypeVariable typeVariable) { - ResolvableType resolvedTypeVariable = resolveVariable( + ResolvableType resolvedTypeVariable = resolveVariableConsiderBound( typeVariable, ResolvableType.forClass(contextClass)); - if (resolvedTypeVariable == ResolvableType.NONE) { - resolvedTypeVariable = ResolvableType.forVariableBounds(typeVariable); - } if (resolvedTypeVariable != ResolvableType.NONE) { Type type = resolvedTypeVariable.getType(); if (type instanceof ParameterizedType) { @@ -179,10 +176,8 @@ else if (genericType instanceof ParameterizedType parameterizedType) { 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) { - resolvedTypeArgument = ResolvableType.forVariableBounds(typeVariable); - } + ResolvableType resolvedTypeArgument = resolveVariableConsiderBound( + typeVariable, contextType); if (resolvedTypeArgument != ResolvableType.NONE) { generics[i] = resolvedTypeArgument; } @@ -190,7 +185,7 @@ else if (genericType instanceof ParameterizedType parameterizedType) { generics[i] = ResolvableType.forType(typeArgument); } } - else if (typeArgument instanceof ParameterizedType) { + else if (typeArgument instanceof ParameterizedType || typeArgument instanceof WildcardType) { generics[i] = ResolvableType.forType(resolveType(typeArgument, contextClass)); } else { @@ -203,10 +198,39 @@ else if (typeArgument instanceof ParameterizedType) { } } } + else if (genericType instanceof WildcardType wildcardType) { + Type[] originalLowerBound = wildcardType.getLowerBounds(); + Type[] originalUpperBound = wildcardType.getUpperBounds(); + + if (originalLowerBound.length == 1) { + Type lowerBound = resolveType(originalLowerBound[0], contextClass); + if (lowerBound != originalLowerBound[0]) { + return ResolvableType.forWildCardTypeWithLowerBound( + wildcardType, ResolvableType.forType(lowerBound)) + .getType(); + } + } else if (originalUpperBound.length == 1) { + Type upperBound = resolveType(originalUpperBound[0], contextClass); + if (upperBound != originalUpperBound[0]) { + return ResolvableType.forWildCardTypeWithUpperBound( + wildcardType, ResolvableType.forType(upperBound)) + .getType(); + } + } + return wildcardType; + } } return genericType; } + private static ResolvableType resolveVariableConsiderBound(TypeVariable typeVariable, ResolvableType contextType) { + ResolvableType resolvedTypeArgument = resolveVariable(typeVariable, contextType); + if (resolvedTypeArgument == ResolvableType.NONE) { + resolvedTypeArgument = ResolvableType.forVariableBounds(typeVariable); + } + return resolvedTypeArgument; + } + private static ResolvableType resolveVariable(TypeVariable typeVariable, ResolvableType contextType) { ResolvableType resolvedType; if (contextType.hasGenerics()) { diff --git a/spring-core/src/main/java/org/springframework/core/ResolvableType.java b/spring-core/src/main/java/org/springframework/core/ResolvableType.java index 9273abd4d11f..206b3326bbd2 100644 --- a/spring-core/src/main/java/org/springframework/core/ResolvableType.java +++ b/spring-core/src/main/java/org/springframework/core/ResolvableType.java @@ -99,6 +99,8 @@ public class ResolvableType implements Serializable { private static final ConcurrentReferenceHashMap cache = new ConcurrentReferenceHashMap<>(256); + private static final Type[] EMPTY_TYPE_ARRAY = new Type[0]; + /** * The underlying Java type being managed. @@ -617,7 +619,8 @@ private boolean determineUnresolvableGenerics(@Nullable Set alreadySeen) { ResolvableType[] generics = getGenerics(); for (ResolvableType generic : generics) { - if (generic.isUnresolvableTypeVariable() || generic.isWildcardWithoutBounds() || + if (generic.isUnresolvableTypeVariable() || + generic.isUnresolvableWildcard(currentTypeSeen(alreadySeen)) || generic.hasUnresolvableGenerics(currentTypeSeen(alreadySeen))) { return true; } @@ -677,14 +680,32 @@ private boolean isWildcardWithoutBounds() { if (this.type instanceof WildcardType wildcardType) { if (wildcardType.getLowerBounds().length == 0) { Type[] upperBounds = wildcardType.getUpperBounds(); - if (upperBounds.length == 0 || (upperBounds.length == 1 && Object.class == upperBounds[0])) { - return true; - } + return upperBounds.length == 0 || (upperBounds.length == 1 && (Object.class == upperBounds[0])); } } return false; } + /** + * Determine whether the underlying type represents a wildcard + * has unresolvable upper bound or lower bound, or simply without bound + */ + private boolean isUnresolvableWildcard(Set alreadySeen) { + if (this.type instanceof WildcardType wildcardType) { + Type[] lowerBounds = wildcardType.getLowerBounds(); + if (lowerBounds.length == 1) { + ResolvableType lowerResolvable = ResolvableType.forType(lowerBounds[0], this.variableResolver); + return lowerResolvable.isUnresolvableTypeVariable() || lowerResolvable.determineUnresolvableGenerics(alreadySeen); + } + Type[] upperBounds = wildcardType.getUpperBounds(); + if (upperBounds.length == 1 && upperBounds[0] != Object.class) { + ResolvableType upperResolvable = ResolvableType.forType(upperBounds[0], this.variableResolver); + return upperResolvable.isUnresolvableTypeVariable() || upperResolvable.determineUnresolvableGenerics(alreadySeen); + } + } + return isWildcardWithoutBounds(); + } + /** * Return a {@code ResolvableType} for the specified nesting level. *

See {@link #getNested(int, Map)} for details. @@ -1185,6 +1206,51 @@ public static ResolvableType forClassWithGenerics(Class clazz, @Nullable Reso (generics != null ? new TypeVariablesVariableResolver(variables, generics) : null)); } + /** + * Return a {@code ResolvableType} for the specified {@link WildcardType} with pre-declared upper bound. + * @param wildcardType the WildcardType to introspect + * @param upperBound the upper bound of the wildcardType + * @return a {@code ResolvableType} for the specific wildcardType and upperBound + */ + public static ResolvableType forWildCardTypeWithUpperBound(WildcardType wildcardType, ResolvableType upperBound) { + Assert.notNull(wildcardType, "WildcardType must not be null"); + Assert.notNull(upperBound, "UpperBound must not be null"); + Type[] originalLowerBound = wildcardType.getLowerBounds(); + Assert.isTrue(originalLowerBound.length == 0, + () -> "The WildcardType has lower bound while upper bound provided " + wildcardType); + + Type upperBoundType = upperBound.getType(); + VariableResolver variableResolver = upperBoundType instanceof TypeVariable typeVariable + ? new TypeVariablesVariableResolver( + new TypeVariable[]{typeVariable}, new ResolvableType[]{upperBound}) + : null; + + return forType(new WildcardTypeImpl(new Type[]{upperBoundType}, EMPTY_TYPE_ARRAY), variableResolver); + } + + /** + * Return a {@code ResolvableType} for the specified {@link WildcardType} with pre-declared lower bound. + * @param wildcardType the WildcardType to introspect + * @param lowerBound the lower bound of the wildcardType + * @return a {@code ResolvableType} for the specific wildcardType and lowerBound + */ + public static ResolvableType forWildCardTypeWithLowerBound(WildcardType wildcardType, ResolvableType lowerBound) { + Assert.notNull(wildcardType, "WildcardType must not be null"); + Assert.notNull(lowerBound, "LowerBound must not be null"); + Type[] originalUpperBound = wildcardType.getUpperBounds(); + Assert.isTrue(originalUpperBound.length == 0 || originalUpperBound[0] == Object.class, + () -> "The WildcardType has upper bound %s while lower bound provided %s" + .formatted(originalUpperBound[0], wildcardType)); + + Type lowerBoundType = lowerBound.getType(); + VariableResolver variableResolver = lowerBoundType instanceof TypeVariable typeVariable + ? new TypeVariablesVariableResolver( + new TypeVariable[]{typeVariable}, new ResolvableType[]{lowerBound}) + : null; + + return forType(new WildcardTypeImpl(new Type[]{Object.class}, new Type[]{lowerBoundType}), variableResolver); + } + /** * Return a {@code ResolvableType} for the specified instance. The instance does not * convey generic information but if it implements {@link ResolvableTypeProvider} a @@ -1634,6 +1700,56 @@ public Object getSource() { } + private static final class WildcardTypeImpl implements WildcardType, Serializable { + + private final Type[] upperBound; + private final Type[] lowerBound; + + private WildcardTypeImpl(Type[] upperBound, Type[] lowerBound) { + this.upperBound = upperBound; + this.lowerBound = lowerBound; + } + + @Override + public Type[] getUpperBounds() { + return upperBound.clone(); + } + + @Override + public Type[] getLowerBounds() { + return lowerBound.clone(); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof WildcardType that)) { + return false; + } + return Arrays.equals(upperBound, that.getUpperBounds()) && Arrays.equals(lowerBound, that.getLowerBounds()); + } + + @Override + public int hashCode() { + return Arrays.hashCode(getLowerBounds()) ^ Arrays.hashCode(getUpperBounds()); + } + + @Override + public String toString() { + if (getLowerBounds().length == 1) { + return "? super " + typeToString(getLowerBounds()[0]); + } + if (getUpperBounds().length == 0 || getUpperBounds()[0] == Object.class) { + return "?"; + } + return "? extends " + typeToString(getUpperBounds()[0]); + } + + private static String typeToString(Type type) { + return type instanceof Class cls ? cls.getName() : type.toString(); + } + } + + private static final class SyntheticParameterizedType implements ParameterizedType, Serializable { private final Type rawType; 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 7e4e3542c021..13dc4d92606c 100644 --- a/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java +++ b/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java @@ -22,6 +22,7 @@ import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -29,6 +30,8 @@ import java.util.function.Supplier; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.core.GenericTypeResolver.getTypeVariableMap; @@ -251,6 +254,14 @@ void resolveTypeFromGenericDefaultMethod() { assertThat(resolvedType).isEqualTo(InheritsDefaultMethod.ConcreteType.class); } + @ParameterizedTest + @ValueSource(strings = {"getUpperBound", "getLowerBound"}) + void resolveTypeFromWildcardType(String methodName) { + Type type = method(MyInterfaceType.class, methodName).getGenericReturnType(); + Type resolvedType = resolveType(type, MySimpleInterfaceType.class); + assertThat(resolvedType).isEqualTo(method(MySimpleInterfaceType.class, methodName).getGenericReturnType()); + } + @Test void resolveTypeFromNestedParameterizedType() { Type resolvedType = resolveType(method(MyInterfaceType.class, "get").getGenericReturnType(), MyCollectionInterfaceType.class); @@ -268,12 +279,29 @@ private static Method method(Class target, String methodName, Class... par public interface MyInterfaceType { + default Optional getUpperBound() { + return Optional.empty(); + } + + default List getLowerBound() { + return Collections.emptyList(); + } + default T get() { return null; } } public class MySimpleInterfaceType implements MyInterfaceType { + @Override + public Optional getUpperBound() { + return MyInterfaceType.super.getUpperBound(); + } + + @Override + public List getLowerBound() { + return MyInterfaceType.super.getLowerBound(); + } } public class MyParameterizedInterfaceType

implements MyInterfaceType> { diff --git a/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java b/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java index 6e07060249a2..4c1576063044 100644 --- a/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java +++ b/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java @@ -42,10 +42,13 @@ import java.util.SortedSet; import java.util.TreeSet; import java.util.concurrent.Callable; +import java.util.stream.Stream; import org.assertj.core.api.AbstractAssert; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.junit.jupiter.MockitoExtension; @@ -1601,6 +1604,31 @@ void gh34541() throws Exception { assertThat(typeWithGenerics.isAssignableFrom(PaymentCreator.class)).isTrue(); } + @ParameterizedTest + @MethodSource("wildcardInfo") + void gh36474(ResolvableType typeVariable, Class resolved) { + assertThat(typeVariable.resolve()).isEqualTo(resolved); + } + + + static Stream wildcardInfo() throws Exception { + WildcardType listxs = getWildcardType(AssignmentBase.class, "listxs"); + WildcardType listsc = getWildcardType(AssignmentBase.class, "listsc"); + ResolvableType owner = ResolvableType.forType(Assignment.class).as(AssignmentBase.class); + + ResolvableType lbWildcard = ResolvableType.forWildCardTypeWithUpperBound( + listxs, ResolvableType.forType(listxs.getUpperBounds()[0], owner)); + ResolvableType ubWildcard = ResolvableType.forWildCardTypeWithLowerBound( + listsc, ResolvableType.forType(listsc.getLowerBounds()[0], owner)); + return Stream.of(new Object[] {lbWildcard, String.class}, new Object[] {ubWildcard, CharSequence.class}); + } + + + static WildcardType getWildcardType(Class cls, String field) throws Exception { + ResolvableType type = ResolvableType.forField(cls.getField(field)); + return (WildcardType) type.getGeneric(0).getType(); + } + private ResolvableType testSerialization(ResolvableType type) throws Exception { ByteArrayOutputStream bos = new ByteArrayOutputStream(); @@ -1631,13 +1659,13 @@ private static ResolvableTypeAssert assertThatResolvableType(ResolvableType type @SuppressWarnings("unused") private HashMap> myMap; - @SuppressWarnings("serial") static class ExtendsList extends ArrayList { - } + } @SuppressWarnings("serial") static class ExtendsMap extends HashMap { + } From b07b77f72abb7e6dae06f7b8d770098d20a4e2d6 Mon Sep 17 00:00:00 2001 From: anaconda875 Date: Mon, 16 Mar 2026 16:07:21 +0700 Subject: [PATCH 3/3] Fix: Checkstyle and refactor Signed-off-by: anaconda875 --- .../core/GenericTypeResolver.java | 14 +++--- .../springframework/core/ResolvableType.java | 45 ++++++++++--------- .../core/GenericTypeResolverTests.java | 5 ++- .../core/ResolvableTypeTests.java | 8 ++-- 4 files changed, 38 insertions(+), 34 deletions(-) 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 1cd981de6c38..1031318e9ed4 100644 --- a/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java +++ b/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java @@ -154,7 +154,7 @@ public static Class resolveReturnType(Method method, Class clazz) { public static Type resolveType(Type genericType, @Nullable Class contextClass) { if (contextClass != null) { if (genericType instanceof TypeVariable typeVariable) { - ResolvableType resolvedTypeVariable = resolveVariableConsiderBound( + ResolvableType resolvedTypeVariable = resolveVariableConsiderBounds( typeVariable, ResolvableType.forClass(contextClass)); if (resolvedTypeVariable != ResolvableType.NONE) { Type type = resolvedTypeVariable.getType(); @@ -176,7 +176,7 @@ else if (genericType instanceof ParameterizedType parameterizedType) { for (int i = 0; i < typeArguments.length; i++) { Type typeArgument = typeArguments[i]; if (typeArgument instanceof TypeVariable typeVariable) { - ResolvableType resolvedTypeArgument = resolveVariableConsiderBound( + ResolvableType resolvedTypeArgument = resolveVariableConsiderBounds( typeVariable, contextType); if (resolvedTypeArgument != ResolvableType.NONE) { generics[i] = resolvedTypeArgument; @@ -205,25 +205,25 @@ else if (genericType instanceof WildcardType wildcardType) { if (originalLowerBound.length == 1) { Type lowerBound = resolveType(originalLowerBound[0], contextClass); if (lowerBound != originalLowerBound[0]) { - return ResolvableType.forWildCardTypeWithLowerBound( + return ResolvableType.forWildcardTypeWithLowerBound( wildcardType, ResolvableType.forType(lowerBound)) .getType(); } - } else if (originalUpperBound.length == 1) { + } + else if (originalUpperBound.length == 1) { Type upperBound = resolveType(originalUpperBound[0], contextClass); if (upperBound != originalUpperBound[0]) { - return ResolvableType.forWildCardTypeWithUpperBound( + return ResolvableType.forWildcardTypeWithUpperBound( wildcardType, ResolvableType.forType(upperBound)) .getType(); } } - return wildcardType; } } return genericType; } - private static ResolvableType resolveVariableConsiderBound(TypeVariable typeVariable, ResolvableType contextType) { + private static ResolvableType resolveVariableConsiderBounds(TypeVariable typeVariable, ResolvableType contextType) { ResolvableType resolvedTypeArgument = resolveVariable(typeVariable, contextType); if (resolvedTypeArgument == ResolvableType.NONE) { resolvedTypeArgument = ResolvableType.forVariableBounds(typeVariable); diff --git a/spring-core/src/main/java/org/springframework/core/ResolvableType.java b/spring-core/src/main/java/org/springframework/core/ResolvableType.java index 206b3326bbd2..7516f7b56395 100644 --- a/spring-core/src/main/java/org/springframework/core/ResolvableType.java +++ b/spring-core/src/main/java/org/springframework/core/ResolvableType.java @@ -620,6 +620,7 @@ private boolean determineUnresolvableGenerics(@Nullable Set alreadySeen) { ResolvableType[] generics = getGenerics(); for (ResolvableType generic : generics) { if (generic.isUnresolvableTypeVariable() || + generic.isWildcardWithoutBounds() || generic.isUnresolvableWildcard(currentTypeSeen(alreadySeen)) || generic.hasUnresolvableGenerics(currentTypeSeen(alreadySeen))) { return true; @@ -688,7 +689,7 @@ private boolean isWildcardWithoutBounds() { /** * Determine whether the underlying type represents a wildcard - * has unresolvable upper bound or lower bound, or simply without bound + * has unresolvable upper bound or lower bound. */ private boolean isUnresolvableWildcard(Set alreadySeen) { if (this.type instanceof WildcardType wildcardType) { @@ -703,7 +704,7 @@ private boolean isUnresolvableWildcard(Set alreadySeen) { return upperResolvable.isUnresolvableTypeVariable() || upperResolvable.determineUnresolvableGenerics(alreadySeen); } } - return isWildcardWithoutBounds(); + return false; } /** @@ -1212,7 +1213,7 @@ public static ResolvableType forClassWithGenerics(Class clazz, @Nullable Reso * @param upperBound the upper bound of the wildcardType * @return a {@code ResolvableType} for the specific wildcardType and upperBound */ - public static ResolvableType forWildCardTypeWithUpperBound(WildcardType wildcardType, ResolvableType upperBound) { + public static ResolvableType forWildcardTypeWithUpperBound(WildcardType wildcardType, ResolvableType upperBound) { Assert.notNull(wildcardType, "WildcardType must not be null"); Assert.notNull(upperBound, "UpperBound must not be null"); Type[] originalLowerBound = wildcardType.getLowerBounds(); @@ -1220,10 +1221,10 @@ public static ResolvableType forWildCardTypeWithUpperBound(WildcardType wildcard () -> "The WildcardType has lower bound while upper bound provided " + wildcardType); Type upperBoundType = upperBound.getType(); - VariableResolver variableResolver = upperBoundType instanceof TypeVariable typeVariable - ? new TypeVariablesVariableResolver( - new TypeVariable[]{typeVariable}, new ResolvableType[]{upperBound}) - : null; + VariableResolver variableResolver = upperBoundType instanceof TypeVariable typeVariable ? + new TypeVariablesVariableResolver( + new TypeVariable[]{typeVariable}, new ResolvableType[]{upperBound}) : + null; return forType(new WildcardTypeImpl(new Type[]{upperBoundType}, EMPTY_TYPE_ARRAY), variableResolver); } @@ -1234,7 +1235,7 @@ public static ResolvableType forWildCardTypeWithUpperBound(WildcardType wildcard * @param lowerBound the lower bound of the wildcardType * @return a {@code ResolvableType} for the specific wildcardType and lowerBound */ - public static ResolvableType forWildCardTypeWithLowerBound(WildcardType wildcardType, ResolvableType lowerBound) { + public static ResolvableType forWildcardTypeWithLowerBound(WildcardType wildcardType, ResolvableType lowerBound) { Assert.notNull(wildcardType, "WildcardType must not be null"); Assert.notNull(lowerBound, "LowerBound must not be null"); Type[] originalUpperBound = wildcardType.getUpperBounds(); @@ -1243,10 +1244,10 @@ public static ResolvableType forWildCardTypeWithLowerBound(WildcardType wildcard .formatted(originalUpperBound[0], wildcardType)); Type lowerBoundType = lowerBound.getType(); - VariableResolver variableResolver = lowerBoundType instanceof TypeVariable typeVariable - ? new TypeVariablesVariableResolver( - new TypeVariable[]{typeVariable}, new ResolvableType[]{lowerBound}) - : null; + VariableResolver variableResolver = lowerBoundType instanceof TypeVariable typeVariable ? + new TypeVariablesVariableResolver( + new TypeVariable[]{typeVariable}, new ResolvableType[]{lowerBound}) : + null; return forType(new WildcardTypeImpl(new Type[]{Object.class}, new Type[]{lowerBoundType}), variableResolver); } @@ -1700,9 +1701,10 @@ public Object getSource() { } - private static final class WildcardTypeImpl implements WildcardType, Serializable { + private static final class WildcardTypeImpl implements WildcardType, Serializable { private final Type[] upperBound; + private final Type[] lowerBound; private WildcardTypeImpl(Type[] upperBound, Type[] lowerBound) { @@ -1712,12 +1714,12 @@ private WildcardTypeImpl(Type[] upperBound, Type[] lowerBound) { @Override public Type[] getUpperBounds() { - return upperBound.clone(); + return this.upperBound.clone(); } @Override public Type[] getLowerBounds() { - return lowerBound.clone(); + return this.lowerBound.clone(); } @Override @@ -1725,23 +1727,24 @@ public boolean equals(Object o) { if (!(o instanceof WildcardType that)) { return false; } - return Arrays.equals(upperBound, that.getUpperBounds()) && Arrays.equals(lowerBound, that.getLowerBounds()); + return Arrays.equals(this.upperBound, that.getUpperBounds()) && + Arrays.equals(this.lowerBound, that.getLowerBounds()); } @Override public int hashCode() { - return Arrays.hashCode(getLowerBounds()) ^ Arrays.hashCode(getUpperBounds()); + return Arrays.hashCode(this.lowerBound) ^ Arrays.hashCode(this.upperBound); } @Override public String toString() { - if (getLowerBounds().length == 1) { - return "? super " + typeToString(getLowerBounds()[0]); + if (this.lowerBound.length == 1) { + return "? super " + typeToString(this.lowerBound[0]); } - if (getUpperBounds().length == 0 || getUpperBounds()[0] == Object.class) { + if (this.upperBound.length == 0 || this.upperBound[0] == Object.class) { return "?"; } - return "? extends " + typeToString(getUpperBounds()[0]); + return "? extends " + typeToString(this.upperBound[0]); } private static String typeToString(Type type) { 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 13dc4d92606c..8378d15cc9b6 100644 --- a/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java +++ b/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java @@ -264,10 +264,11 @@ void resolveTypeFromWildcardType(String methodName) { @Test void resolveTypeFromNestedParameterizedType() { - Type resolvedType = resolveType(method(MyInterfaceType.class, "get").getGenericReturnType(), MyCollectionInterfaceType.class); + Type rawReturnType = method(MyInterfaceType.class, "get").getGenericReturnType(); + Type resolvedType = resolveType(rawReturnType, MyCollectionInterfaceType.class); assertThat(resolvedType).isEqualTo(method(MyCollectionInterfaceType.class, "get").getGenericReturnType()); - resolvedType = resolveType(method(MyInterfaceType.class, "get").getGenericReturnType(), MyOptionalInterfaceType.class); + resolvedType = resolveType(rawReturnType, MyOptionalInterfaceType.class); assertThat(resolvedType).isEqualTo(method(MyOptionalInterfaceType.class, "get").getGenericReturnType()); } diff --git a/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java b/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java index 4c1576063044..2b3684b2ceb2 100644 --- a/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java +++ b/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java @@ -1616,9 +1616,9 @@ static Stream wildcardInfo() throws Exception { WildcardType listsc = getWildcardType(AssignmentBase.class, "listsc"); ResolvableType owner = ResolvableType.forType(Assignment.class).as(AssignmentBase.class); - ResolvableType lbWildcard = ResolvableType.forWildCardTypeWithUpperBound( + ResolvableType lbWildcard = ResolvableType.forWildcardTypeWithUpperBound( listxs, ResolvableType.forType(listxs.getUpperBounds()[0], owner)); - ResolvableType ubWildcard = ResolvableType.forWildCardTypeWithLowerBound( + ResolvableType ubWildcard = ResolvableType.forWildcardTypeWithLowerBound( listsc, ResolvableType.forType(listsc.getLowerBounds()[0], owner)); return Stream.of(new Object[] {lbWildcard, String.class}, new Object[] {ubWildcard, CharSequence.class}); } @@ -1659,13 +1659,13 @@ private static ResolvableTypeAssert assertThatResolvableType(ResolvableType type @SuppressWarnings("unused") private HashMap> myMap; + @SuppressWarnings("serial") static class ExtendsList extends ArrayList { - } + @SuppressWarnings("serial") static class ExtendsMap extends HashMap { - }