2828import java .lang .reflect .ParameterizedType ;
2929import java .lang .reflect .Type ;
3030import java .lang .reflect .TypeVariable ;
31+ import java .lang .reflect .WildcardType ;
3132import java .security .AccessController ;
3233import java .security .PrivilegedExceptionAction ;
3334import java .util .Arrays ;
3435import java .util .Collections ;
3536import java .util .HashMap ;
3637import java .util .Map ;
38+ import java .util .Objects ;
3739import java .util .WeakHashMap ;
3840
3941import sun .misc .Unsafe ;
@@ -56,6 +58,7 @@ public final class TypeResolver {
5658 private static final Map <String , Method > OBJECT_METHODS = new HashMap <String , Method >();
5759 private static final Map <Class <?>, Class <?>> PRIMITIVE_WRAPPERS ;
5860 private static final Double JAVA_VERSION ;
61+ private static final String NEW_ISSUE_URL = "https://github.com/jhalterman/typetools/issues/new" ;
5962
6063 static {
6164 JAVA_VERSION = Double .parseDouble (System .getProperty ("java.specification.version" , "0" ));
@@ -115,6 +118,58 @@ private Unknown() {
115118 }
116119 }
117120
121+ private static class ResolvedParameterizedType implements ParameterizedType {
122+ private final ParameterizedType original ;
123+ private final Type [] resolvedTypeArguments ;
124+
125+ private ResolvedParameterizedType (ParameterizedType original , Type [] resolvedTypeArguments ) {
126+ Objects .requireNonNull (original );
127+ Objects .requireNonNull (resolvedTypeArguments );
128+
129+ this .original = original ;
130+ this .resolvedTypeArguments = resolvedTypeArguments ;
131+ }
132+
133+ public Type [] getGenericActualTypeArguments () {
134+ return original .getActualTypeArguments ();
135+ }
136+
137+ @ Override
138+ public Type [] getActualTypeArguments () {
139+ return resolvedTypeArguments ;
140+ }
141+
142+ @ Override
143+ public Type getRawType () {
144+ return original .getRawType ();
145+ }
146+
147+ @ Override
148+ public Type getOwnerType () {
149+ return original .getOwnerType ();
150+ }
151+
152+ @ Override
153+ public boolean equals (Object o ) {
154+ if (this == o ) {
155+ return true ;
156+ }
157+ if (o == null || getClass () != o .getClass ()) {
158+ return false ;
159+ }
160+ ResolvedParameterizedType that = (ResolvedParameterizedType ) o ;
161+ return original .equals (that .original ) &&
162+ Arrays .equals (resolvedTypeArguments , that .resolvedTypeArguments );
163+ }
164+
165+ @ Override
166+ public int hashCode () {
167+ int result = Objects .hash (original );
168+ result = 31 * result + Arrays .hashCode (resolvedTypeArguments );
169+ return result ;
170+ }
171+ }
172+
118173 private TypeResolver () {
119174 }
120175
@@ -182,6 +237,14 @@ public static <T, S extends T> Class<?>[] resolveRawArguments(Class<T> type, Cla
182237 return resolveRawArguments (resolveGenericType (type , subType ), subType );
183238 }
184239
240+ public static <T , S extends T > Type resolveType (Class <T > type , Class <S > subType ) {
241+ return substituteGenerics (resolveGenericType (type , subType ), getTypeVariableMap (subType , null ));
242+ }
243+
244+ public static Type resolveType (Type type , Class <?> context ) {
245+ return substituteGenerics (type , getTypeVariableMap (context , null ));
246+ }
247+
185248 /**
186249 * Returns an array of raw classes representing arguments for the {@code genericType} using type variable information
187250 * from the {@code subType}. Arguments for {@code genericType} that cannot be resolved are returned as
@@ -334,6 +397,58 @@ private static Map<TypeVariable<?>, Type> getTypeVariableMap(final Class<?> targ
334397 return map ;
335398 }
336399
400+ private static Type substituteGenerics (final Type genericType , final Map <TypeVariable <?>, Type > typeVariableMap ) {
401+ if (genericType == null ) {
402+ return null ;
403+ }
404+ if (genericType instanceof Class <?>) {
405+ return genericType ;
406+ }
407+ if (genericType instanceof TypeVariable <?>) {
408+ final TypeVariable <?> typeVariable = (TypeVariable <?>) genericType ;
409+ final Type mapping = typeVariableMap .get (typeVariable );
410+ if (mapping != null ) {
411+ return substituteGenerics (mapping , typeVariableMap );
412+ }
413+ final Type [] upperBounds = typeVariable .getBounds ();
414+ // NOTE: According to https://docs.oracle.com/javase/tutorial/java/generics/bounded.html
415+ // if there are multiple upper bounds where one bound is a class, then this must be the
416+ // leftmost/first bound. Therefore we blindly take this one, hoping is the most relevant.
417+ return substituteGenerics (upperBounds [0 ], typeVariableMap );
418+ }
419+ if (genericType instanceof WildcardType ) {
420+ final WildcardType wildcardType = (WildcardType ) genericType ;
421+ final Type [] upperBounds = wildcardType .getUpperBounds ();
422+ final Type [] lowerBounds = wildcardType .getLowerBounds ();
423+ if (upperBounds .length == 1 && lowerBounds .length == 0 ) {
424+ return substituteGenerics (upperBounds [0 ], typeVariableMap );
425+ }
426+ throw new UnsupportedOperationException (
427+ "Resolution of wildcard types is only supported for the trivial case of exactly one upper bound " +
428+ "and no lower bounds. If you require resolution in a more complex case, please file an issue via " +
429+ NEW_ISSUE_URL
430+ );
431+ }
432+ if (genericType instanceof ParameterizedType ) {
433+ final ParameterizedType parameterizedType = (ParameterizedType ) genericType ;
434+ final Type [] actualTypeArguments = parameterizedType .getActualTypeArguments ();
435+ final Type [] resolvedTypeArguments = new Type [actualTypeArguments .length ];
436+
437+ boolean changed = false ;
438+ for (int i = 0 ; i < actualTypeArguments .length ; i ++) {
439+ resolvedTypeArguments [i ] = substituteGenerics (actualTypeArguments [i ], typeVariableMap );
440+ changed = changed || (resolvedTypeArguments [i ] != actualTypeArguments [i ]);
441+ }
442+
443+ return changed ? new ResolvedParameterizedType (parameterizedType , resolvedTypeArguments ) : parameterizedType ;
444+ }
445+ throw new UnsupportedOperationException (
446+ "Cannot substitute generics for type with name '" + genericType .getTypeName () + "' and " +
447+ "class name '" + genericType .getClass ().getName () + "'. Please file an issue including this message via " +
448+ NEW_ISSUE_URL
449+ );
450+ }
451+
337452 /**
338453 * Populates the {@code map} with with variable/argument pairs for the given {@code types}.
339454 */
0 commit comments