@@ -98,6 +98,8 @@ public class ResolvableType implements Serializable {
9898 private static final ConcurrentReferenceHashMap <ResolvableType , ResolvableType > cache =
9999 new ConcurrentReferenceHashMap <>(256 );
100100
101+ private static final Type [] EMPTY_TYPE_ARRAY = new Type [0 ];
102+
101103
102104 /**
103105 * The underlying Java type being managed.
@@ -616,7 +618,8 @@ private boolean determineUnresolvableGenerics(@Nullable Set<Type> alreadySeen) {
616618
617619 ResolvableType [] generics = getGenerics ();
618620 for (ResolvableType generic : generics ) {
619- if (generic .isUnresolvableTypeVariable () || generic .isWildcardWithoutBounds () ||
621+ if (generic .isUnresolvableTypeVariable () ||
622+ generic .isUnresolvableWildcard (currentTypeSeen (alreadySeen )) ||
620623 generic .hasUnresolvableGenerics (currentTypeSeen (alreadySeen ))) {
621624 return true ;
622625 }
@@ -676,14 +679,32 @@ private boolean isWildcardWithoutBounds() {
676679 if (this .type instanceof WildcardType wildcardType ) {
677680 if (wildcardType .getLowerBounds ().length == 0 ) {
678681 Type [] upperBounds = wildcardType .getUpperBounds ();
679- if (upperBounds .length == 0 || (upperBounds .length == 1 && Object .class == upperBounds [0 ])) {
680- return true ;
681- }
682+ return upperBounds .length == 0 || (upperBounds .length == 1 && (Object .class == upperBounds [0 ]));
682683 }
683684 }
684685 return false ;
685686 }
686687
688+ /**
689+ * Determine whether the underlying type represents a wildcard
690+ * has unresolvable upper bound or lower bound, or simply without bound
691+ */
692+ private boolean isUnresolvableWildcard (Set <Type > alreadySeen ) {
693+ if (this .type instanceof WildcardType wildcardType ) {
694+ Type [] lowerBounds = wildcardType .getLowerBounds ();
695+ if (lowerBounds .length == 1 ) {
696+ ResolvableType lowerResolvable = ResolvableType .forType (lowerBounds [0 ], this .variableResolver );
697+ return lowerResolvable .isUnresolvableTypeVariable () || lowerResolvable .determineUnresolvableGenerics (alreadySeen );
698+ }
699+ Type [] upperBounds = wildcardType .getUpperBounds ();
700+ if (upperBounds .length == 1 && upperBounds [0 ] != Object .class ) {
701+ ResolvableType upperResolvable = ResolvableType .forType (upperBounds [0 ], this .variableResolver );
702+ return upperResolvable .isUnresolvableTypeVariable () || upperResolvable .determineUnresolvableGenerics (alreadySeen );
703+ }
704+ }
705+ return isWildcardWithoutBounds ();
706+ }
707+
687708 /**
688709 * Return a {@code ResolvableType} for the specified nesting level.
689710 * <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
@@ -1628,6 +1694,56 @@ public Object getSource() {
16281694 }
16291695
16301696
1697+ private static final class WildcardTypeImpl implements WildcardType , Serializable {
1698+
1699+ private final Type [] upperBound ;
1700+ private final Type [] lowerBound ;
1701+
1702+ private WildcardTypeImpl (Type [] upperBound , Type [] lowerBound ) {
1703+ this .upperBound = upperBound ;
1704+ this .lowerBound = lowerBound ;
1705+ }
1706+
1707+ @ Override
1708+ public Type [] getUpperBounds () {
1709+ return upperBound .clone ();
1710+ }
1711+
1712+ @ Override
1713+ public Type [] getLowerBounds () {
1714+ return lowerBound .clone ();
1715+ }
1716+
1717+ @ Override
1718+ public boolean equals (Object o ) {
1719+ if (!(o instanceof WildcardType that )) {
1720+ return false ;
1721+ }
1722+ return Arrays .equals (upperBound , that .getUpperBounds ()) && Arrays .equals (lowerBound , that .getLowerBounds ());
1723+ }
1724+
1725+ @ Override
1726+ public int hashCode () {
1727+ return Arrays .hashCode (getLowerBounds ()) ^ Arrays .hashCode (getUpperBounds ());
1728+ }
1729+
1730+ @ Override
1731+ public String toString () {
1732+ if (getLowerBounds ().length == 1 ) {
1733+ return "? super " + typeToString (getLowerBounds ()[0 ]);
1734+ }
1735+ if (getUpperBounds ().length == 0 || getUpperBounds ()[0 ] == Object .class ) {
1736+ return "?" ;
1737+ }
1738+ return "? extends " + typeToString (getUpperBounds ()[0 ]);
1739+ }
1740+
1741+ private static String typeToString (Type type ) {
1742+ return type instanceof Class <?> cls ? cls .getName () : type .toString ();
1743+ }
1744+ }
1745+
1746+
16311747 private static final class SyntheticParameterizedType implements ParameterizedType , Serializable {
16321748
16331749 private final Type rawType ;
0 commit comments