@@ -99,6 +99,8 @@ public class ResolvableType implements Serializable {
9999 private static final ConcurrentReferenceHashMap <ResolvableType , ResolvableType > cache =
100100 new ConcurrentReferenceHashMap <>(256 );
101101
102+ private static final Type [] EMPTY_TYPE_ARRAY = new Type [0 ];
103+
102104
103105 /**
104106 * The underlying Java type being managed.
@@ -617,7 +619,8 @@ private boolean determineUnresolvableGenerics(@Nullable Set<Type> alreadySeen) {
617619
618620 ResolvableType [] generics = getGenerics ();
619621 for (ResolvableType generic : generics ) {
620- if (generic .isUnresolvableTypeVariable () || generic .isWildcardWithoutBounds () ||
622+ if (generic .isUnresolvableTypeVariable () ||
623+ generic .isUnresolvableWildcard (currentTypeSeen (alreadySeen )) ||
621624 generic .hasUnresolvableGenerics (currentTypeSeen (alreadySeen ))) {
622625 return true ;
623626 }
@@ -677,14 +680,32 @@ private boolean isWildcardWithoutBounds() {
677680 if (this .type instanceof WildcardType wildcardType ) {
678681 if (wildcardType .getLowerBounds ().length == 0 ) {
679682 Type [] upperBounds = wildcardType .getUpperBounds ();
680- if (upperBounds .length == 0 || (upperBounds .length == 1 && Object .class == upperBounds [0 ])) {
681- return true ;
682- }
683+ return upperBounds .length == 0 || (upperBounds .length == 1 && (Object .class == upperBounds [0 ]));
683684 }
684685 }
685686 return false ;
686687 }
687688
689+ /**
690+ * Determine whether the underlying type represents a wildcard
691+ * has unresolvable upper bound or lower bound, or simply without bound
692+ */
693+ private boolean isUnresolvableWildcard (Set <Type > alreadySeen ) {
694+ if (this .type instanceof WildcardType wildcardType ) {
695+ Type [] lowerBounds = wildcardType .getLowerBounds ();
696+ if (lowerBounds .length == 1 ) {
697+ ResolvableType lowerResolvable = ResolvableType .forType (lowerBounds [0 ], this .variableResolver );
698+ return lowerResolvable .isUnresolvableTypeVariable () || lowerResolvable .determineUnresolvableGenerics (alreadySeen );
699+ }
700+ Type [] upperBounds = wildcardType .getUpperBounds ();
701+ if (upperBounds .length == 1 && upperBounds [0 ] != Object .class ) {
702+ ResolvableType upperResolvable = ResolvableType .forType (upperBounds [0 ], this .variableResolver );
703+ return upperResolvable .isUnresolvableTypeVariable () || upperResolvable .determineUnresolvableGenerics (alreadySeen );
704+ }
705+ }
706+ return isWildcardWithoutBounds ();
707+ }
708+
688709 /**
689710 * Return a {@code ResolvableType} for the specified nesting level.
690711 * <p>See {@link #getNested(int, Map)} for details.
@@ -1185,6 +1206,51 @@ public static ResolvableType forClassWithGenerics(Class<?> clazz, @Nullable Reso
11851206 (generics != null ? new TypeVariablesVariableResolver (variables , generics ) : null ));
11861207 }
11871208
1209+ /**
1210+ * Return a {@code ResolvableType} for the specified {@link WildcardType} with pre-declared upper bound.
1211+ * @param wildcardType the WildcardType to introspect
1212+ * @param upperBound the upper bound of the wildcardType
1213+ * @return a {@code ResolvableType} for the specific wildcardType and upperBound
1214+ */
1215+ public static ResolvableType forWildCardTypeWithUpperBound (WildcardType wildcardType , ResolvableType upperBound ) {
1216+ Assert .notNull (wildcardType , "WildcardType must not be null" );
1217+ Assert .notNull (upperBound , "UpperBound must not be null" );
1218+ Type [] originalLowerBound = wildcardType .getLowerBounds ();
1219+ Assert .isTrue (originalLowerBound .length == 0 ,
1220+ () -> "The WildcardType has lower bound while upper bound provided " + wildcardType );
1221+
1222+ Type upperBoundType = upperBound .getType ();
1223+ VariableResolver variableResolver = upperBoundType instanceof TypeVariable <?> typeVariable
1224+ ? new TypeVariablesVariableResolver (
1225+ new TypeVariable <?>[]{typeVariable }, new ResolvableType []{upperBound })
1226+ : null ;
1227+
1228+ return forType (new WildcardTypeImpl (new Type []{upperBoundType }, EMPTY_TYPE_ARRAY ), variableResolver );
1229+ }
1230+
1231+ /**
1232+ * Return a {@code ResolvableType} for the specified {@link WildcardType} with pre-declared lower bound.
1233+ * @param wildcardType the WildcardType to introspect
1234+ * @param lowerBound the lower bound of the wildcardType
1235+ * @return a {@code ResolvableType} for the specific wildcardType and lowerBound
1236+ */
1237+ public static ResolvableType forWildCardTypeWithLowerBound (WildcardType wildcardType , ResolvableType lowerBound ) {
1238+ Assert .notNull (wildcardType , "WildcardType must not be null" );
1239+ Assert .notNull (lowerBound , "LowerBound must not be null" );
1240+ Type [] originalUpperBound = wildcardType .getUpperBounds ();
1241+ Assert .isTrue (originalUpperBound .length == 0 || originalUpperBound [0 ] == Object .class ,
1242+ () -> "The WildcardType has upper bound %s while lower bound provided %s"
1243+ .formatted (originalUpperBound [0 ], wildcardType ));
1244+
1245+ Type lowerBoundType = lowerBound .getType ();
1246+ VariableResolver variableResolver = lowerBoundType instanceof TypeVariable <?> typeVariable
1247+ ? new TypeVariablesVariableResolver (
1248+ new TypeVariable <?>[]{typeVariable }, new ResolvableType []{lowerBound })
1249+ : null ;
1250+
1251+ return forType (new WildcardTypeImpl (new Type []{Object .class }, new Type []{lowerBoundType }), variableResolver );
1252+ }
1253+
11881254 /**
11891255 * Return a {@code ResolvableType} for the specified instance. The instance does not
11901256 * convey generic information but if it implements {@link ResolvableTypeProvider} a
@@ -1634,6 +1700,56 @@ public Object getSource() {
16341700 }
16351701
16361702
1703+ private static final class WildcardTypeImpl implements WildcardType , Serializable {
1704+
1705+ private final Type [] upperBound ;
1706+ private final Type [] lowerBound ;
1707+
1708+ private WildcardTypeImpl (Type [] upperBound , Type [] lowerBound ) {
1709+ this .upperBound = upperBound ;
1710+ this .lowerBound = lowerBound ;
1711+ }
1712+
1713+ @ Override
1714+ public Type [] getUpperBounds () {
1715+ return upperBound .clone ();
1716+ }
1717+
1718+ @ Override
1719+ public Type [] getLowerBounds () {
1720+ return lowerBound .clone ();
1721+ }
1722+
1723+ @ Override
1724+ public boolean equals (Object o ) {
1725+ if (!(o instanceof WildcardType that )) {
1726+ return false ;
1727+ }
1728+ return Arrays .equals (upperBound , that .getUpperBounds ()) && Arrays .equals (lowerBound , that .getLowerBounds ());
1729+ }
1730+
1731+ @ Override
1732+ public int hashCode () {
1733+ return Arrays .hashCode (getLowerBounds ()) ^ Arrays .hashCode (getUpperBounds ());
1734+ }
1735+
1736+ @ Override
1737+ public String toString () {
1738+ if (getLowerBounds ().length == 1 ) {
1739+ return "? super " + typeToString (getLowerBounds ()[0 ]);
1740+ }
1741+ if (getUpperBounds ().length == 0 || getUpperBounds ()[0 ] == Object .class ) {
1742+ return "?" ;
1743+ }
1744+ return "? extends " + typeToString (getUpperBounds ()[0 ]);
1745+ }
1746+
1747+ private static String typeToString (Type type ) {
1748+ return type instanceof Class <?> cls ? cls .getName () : type .toString ();
1749+ }
1750+ }
1751+
1752+
16371753 private static final class SyntheticParameterizedType implements ParameterizedType , Serializable {
16381754
16391755 private final Type rawType ;
0 commit comments