View Javadoc
1   /*
2    * MIT License
3    * Copyright (c) 2006-2025 JMockit developers
4    * See LICENSE file for full license text.
5    */
6   package mockit.internal.reflection;
7   
8   import edu.umd.cs.findbugs.annotations.NonNull;
9   import edu.umd.cs.findbugs.annotations.Nullable;
10  
11  import java.lang.reflect.GenericArrayType;
12  import java.lang.reflect.GenericDeclaration;
13  import java.lang.reflect.Member;
14  import java.lang.reflect.ParameterizedType;
15  import java.lang.reflect.Type;
16  import java.lang.reflect.TypeVariable;
17  import java.lang.reflect.WildcardType;
18  import java.util.ArrayList;
19  import java.util.Collections;
20  import java.util.HashMap;
21  import java.util.List;
22  import java.util.Map;
23  import java.util.Map.Entry;
24  
25  @SuppressWarnings("OverlyComplexClass")
26  public final class GenericTypeReflection {
27      @NonNull
28      private final Map<String, Type> typeParametersToTypeArguments;
29      @NonNull
30      private final Map<String, String> typeParametersToTypeArgumentNames;
31      private final boolean withSignatures;
32  
33      public GenericTypeReflection(@NonNull Class<?> ownerClass, @Nullable Type genericType) {
34          this(ownerClass, genericType, true);
35      }
36  
37      public GenericTypeReflection(@NonNull Class<?> ownerClass, @Nullable Type genericType, boolean withSignatures) {
38          typeParametersToTypeArguments = new HashMap<>(4);
39          typeParametersToTypeArgumentNames = withSignatures ? new HashMap<>(4) : Collections.<String, String> emptyMap();
40          this.withSignatures = withSignatures;
41          discoverTypeMappings(ownerClass, genericType);
42      }
43  
44      private void discoverTypeMappings(@NonNull Class<?> rawType, @Nullable Type genericType) {
45          if (genericType instanceof ParameterizedType) {
46              addMappingsFromTypeParametersToTypeArguments(rawType, (ParameterizedType) genericType);
47          }
48  
49          addGenericTypeMappingsForSuperTypes(rawType);
50      }
51  
52      private void addGenericTypeMappingsForSuperTypes(@NonNull Class<?> rawType) {
53          Type superType = rawType;
54  
55          while (superType != null && superType != Object.class) {
56              Class<?> superClass = (Class<?>) superType;
57              superType = superClass.getGenericSuperclass();
58  
59              if (superType != null && superType != Object.class) {
60                  superClass = addGenericTypeMappingsIfParameterized(superType);
61                  superType = superClass;
62              }
63  
64              addGenericTypeMappingsForInterfaces(superClass);
65          }
66      }
67  
68      @NonNull
69      private Class<?> addGenericTypeMappingsIfParameterized(@NonNull Type superType) {
70          if (superType instanceof ParameterizedType) {
71              ParameterizedType genericSuperType = (ParameterizedType) superType;
72              Class<?> rawType = (Class<?>) genericSuperType.getRawType();
73              addMappingsFromTypeParametersToTypeArguments(rawType, genericSuperType);
74              return rawType;
75          }
76  
77          return (Class<?>) superType;
78      }
79  
80      private void addGenericTypeMappingsForInterfaces(@NonNull Class<?> classOrInterface) {
81          for (Type implementedInterface : classOrInterface.getGenericInterfaces()) {
82              Class<?> implementedType = addGenericTypeMappingsIfParameterized(implementedInterface);
83              addGenericTypeMappingsForInterfaces(implementedType);
84          }
85      }
86  
87      private void addMappingsFromTypeParametersToTypeArguments(@NonNull Class<?> rawType,
88              @NonNull ParameterizedType genericType) {
89          String ownerTypeDesc = getOwnerClassDesc(rawType);
90          TypeVariable<?>[] typeParameters = rawType.getTypeParameters();
91          Type[] typeArguments = genericType.getActualTypeArguments();
92  
93          for (int i = 0, n = typeParameters.length; i < n; i++) {
94              TypeVariable<?> typeParam = typeParameters[i];
95              String typeVarName = typeParam.getName();
96  
97              if (typeParametersToTypeArguments.containsKey(ownerTypeDesc + ':' + typeVarName)) {
98                  continue;
99              }
100 
101             Type typeArg = typeArguments[i];
102 
103             if (typeArg instanceof Class<?>) {
104                 addMappingForClassType(ownerTypeDesc, typeVarName, typeArg);
105             } else if (typeArg instanceof TypeVariable<?>) {
106                 addMappingForTypeVariable(ownerTypeDesc, typeVarName, typeArg);
107             } else if (typeArg instanceof ParameterizedType) {
108                 addMappingForParameterizedType(ownerTypeDesc, typeVarName, typeArg);
109             } else if (typeArg instanceof GenericArrayType) {
110                 addMappingForArrayType(ownerTypeDesc, typeVarName, typeArg);
111             } else {
112                 addMappingForFirstTypeBound(ownerTypeDesc, typeParam);
113             }
114         }
115 
116         Type outerType = genericType.getOwnerType();
117 
118         if (outerType instanceof ParameterizedType) {
119             ParameterizedType parameterizedOuterType = (ParameterizedType) outerType;
120             Class<?> rawOuterType = (Class<?>) parameterizedOuterType.getRawType();
121             addMappingsFromTypeParametersToTypeArguments(rawOuterType, parameterizedOuterType);
122         }
123     }
124 
125     private void addMappingForClassType(@NonNull String ownerTypeDesc, @NonNull String typeName,
126             @NonNull Type typeArg) {
127         String mappedTypeArgName = null;
128 
129         if (withSignatures) {
130             Class<?> classArg = (Class<?>) typeArg;
131             String ownerClassDesc = getOwnerClassDesc(classArg);
132             mappedTypeArgName = classArg.isArray() ? ownerClassDesc : 'L' + ownerClassDesc;
133         }
134 
135         addTypeMapping(ownerTypeDesc, typeName, typeArg, mappedTypeArgName);
136     }
137 
138     private void addMappingForTypeVariable(@NonNull String ownerTypeDesc, @NonNull String typeName,
139             @NonNull Type typeArg) {
140         @Nullable
141         String mappedTypeArgName = null;
142 
143         if (withSignatures) {
144             TypeVariable<?> typeVar = (TypeVariable<?>) typeArg;
145             String ownerClassDesc = getOwnerClassDesc(typeVar);
146             String intermediateTypeArg = ownerClassDesc + ":T" + typeVar.getName();
147             mappedTypeArgName = typeParametersToTypeArgumentNames.get(intermediateTypeArg);
148         }
149 
150         addTypeMapping(ownerTypeDesc, typeName, typeArg, mappedTypeArgName);
151     }
152 
153     private void addMappingForParameterizedType(@NonNull String ownerTypeDesc, @NonNull String typeName,
154             @NonNull Type typeArg) {
155         String mappedTypeArgName = getMappedTypeArgName(typeArg);
156         addTypeMapping(ownerTypeDesc, typeName, typeArg, mappedTypeArgName);
157     }
158 
159     @Nullable
160     private String getMappedTypeArgName(@NonNull Type typeArg) {
161         if (withSignatures) {
162             Class<?> classType = getClassType(typeArg);
163             return 'L' + getOwnerClassDesc(classType);
164         }
165 
166         return null;
167     }
168 
169     private void addMappingForArrayType(@NonNull String ownerTypeDesc, @NonNull String typeName,
170             @NonNull Type typeArg) {
171         String mappedTypeArgName = null;
172 
173         if (withSignatures) {
174             mappedTypeArgName = getMappedTypeArgName((GenericArrayType) typeArg);
175         }
176 
177         addTypeMapping(ownerTypeDesc, typeName, typeArg, mappedTypeArgName);
178     }
179 
180     private void addMappingForFirstTypeBound(@NonNull String ownerTypeDesc, @NonNull TypeVariable<?> typeParam) {
181         Type typeArg = typeParam.getBounds()[0];
182         String mappedTypeArgName = getMappedTypeArgName(typeArg);
183         addTypeMapping(ownerTypeDesc, typeParam.getName(), typeArg, mappedTypeArgName);
184     }
185 
186     @NonNull
187     private static String getOwnerClassDesc(@NonNull Class<?> rawType) {
188         return rawType.getName().replace('.', '/');
189     }
190 
191     @NonNull
192     private Class<?> getClassType(@NonNull Type type) {
193         if (type instanceof ParameterizedType) {
194             ParameterizedType parameterizedType = (ParameterizedType) type;
195             return (Class<?>) parameterizedType.getRawType();
196         }
197 
198         if (type instanceof TypeVariable<?>) {
199             TypeVariable<?> typeVar = (TypeVariable<?>) type;
200             String typeVarKey = getTypeVariableKey(typeVar);
201             @Nullable
202             Type typeArg = typeParametersToTypeArguments.get(typeVarKey);
203 
204             if (typeArg == null) {
205                 throw new IllegalArgumentException("Unable to resolve type variable \"" + typeVar.getName() + '"');
206             }
207 
208             // noinspection TailRecursion
209             return getClassType(typeArg);
210         }
211 
212         return (Class<?>) type;
213     }
214 
215     @NonNull
216     private String getMappedTypeArgName(@NonNull GenericArrayType arrayType) {
217         StringBuilder argName = new StringBuilder(20);
218         argName.append('[');
219 
220         while (true) {
221             Type componentType = arrayType.getGenericComponentType();
222 
223             if (!(componentType instanceof GenericArrayType)) {
224                 Class<?> classType = getClassType(componentType);
225                 argName.append('L').append(getOwnerClassDesc(classType));
226                 return argName.toString();
227             }
228             argName.append('[');
229             // noinspection AssignmentToMethodParameter
230             arrayType = (GenericArrayType) componentType;
231         }
232     }
233 
234     private void addTypeMapping(@NonNull String ownerTypeDesc, @NonNull String typeVarName, @NonNull Type mappedTypeArg,
235             @Nullable String mappedTypeArgName) {
236         typeParametersToTypeArguments.put(ownerTypeDesc + ':' + typeVarName, mappedTypeArg);
237 
238         if (mappedTypeArgName != null) {
239             addTypeMapping(ownerTypeDesc, typeVarName, mappedTypeArgName);
240         }
241     }
242 
243     private void addTypeMapping(@NonNull String ownerTypeDesc, @NonNull String typeVarName,
244             @NonNull String mappedTypeArgName) {
245         String typeMappingKey = ownerTypeDesc + ":T" + typeVarName;
246         typeParametersToTypeArgumentNames.put(typeMappingKey, mappedTypeArgName);
247     }
248 
249     public final class GenericSignature {
250         private final List<String> parameters = new ArrayList<>();
251         private final String parameterTypeDescs;
252         private final int lengthOfParameterTypeDescs;
253         private int currentPos;
254 
255         GenericSignature(@NonNull String signature) {
256             int p = signature.indexOf('(');
257             int q = signature.lastIndexOf(')');
258             parameterTypeDescs = signature.substring(p + 1, q);
259             lengthOfParameterTypeDescs = parameterTypeDescs.length();
260             addTypeDescsToList();
261         }
262 
263         private void addTypeDescsToList() {
264             while (currentPos < lengthOfParameterTypeDescs) {
265                 addNextParameter();
266             }
267         }
268 
269         private void addNextParameter() {
270             int startPos = currentPos;
271             int endPos;
272             char c = parameterTypeDescs.charAt(startPos);
273 
274             switch (c) {
275                 case 'T':
276                     endPos = parameterTypeDescs.indexOf(';', startPos);
277                     currentPos = endPos;
278                     break;
279                 case 'L':
280                     endPos = advanceToEndOfTypeDesc();
281                     break;
282                 case '[': {
283                     char elemTypeStart = firstCharacterOfArrayElementType();
284                     if (elemTypeStart == 'T') {
285                         endPos = parameterTypeDescs.indexOf(';', startPos);
286                         currentPos = endPos;
287                     } else if (elemTypeStart == 'L') {
288                         endPos = advanceToEndOfTypeDesc();
289                     } else {
290                         endPos = currentPos + 1;
291                     }
292                     break;
293                 }
294                 default:
295                     endPos = currentPos + 1;
296                     break;
297             }
298 
299             currentPos++;
300             String parameter = parameterTypeDescs.substring(startPos, endPos);
301             parameters.add(parameter);
302         }
303 
304         private int advanceToEndOfTypeDesc() {
305             char c = '\0';
306 
307             do {
308                 currentPos++;
309                 if (currentPos == lengthOfParameterTypeDescs) {
310                     break;
311                 }
312                 c = parameterTypeDescs.charAt(currentPos);
313             } while (c != ';' && c != '<');
314 
315             int endPos = currentPos;
316 
317             if (c == '<') {
318                 advancePastTypeArguments();
319                 currentPos++;
320             }
321 
322             return endPos;
323         }
324 
325         private char firstCharacterOfArrayElementType() {
326             char c;
327 
328             do {
329                 currentPos++;
330                 c = parameterTypeDescs.charAt(currentPos);
331             } while (c == '[');
332 
333             return c;
334         }
335 
336         private void advancePastTypeArguments() {
337             int angleBracketDepth = 1;
338 
339             do {
340                 currentPos++;
341                 char c = parameterTypeDescs.charAt(currentPos);
342                 if (c == '>') {
343                     angleBracketDepth--;
344                 } else if (c == '<') {
345                     angleBracketDepth++;
346                 }
347             } while (angleBracketDepth > 0);
348         }
349 
350         public boolean satisfiesGenericSignature(@NonNull String otherSignature) {
351             GenericSignature other = new GenericSignature(otherSignature);
352             return areMatchingSignatures(other);
353         }
354 
355         private boolean areMatchingSignatures(@NonNull GenericSignature other) {
356             int n = parameters.size();
357 
358             if (n != other.parameters.size()) {
359                 return false;
360             }
361 
362             for (int i = 0; i < n; i++) {
363                 String p1 = other.parameters.get(i);
364                 String p2 = parameters.get(i);
365 
366                 if (!areParametersOfSameType(p1, p2)) {
367                     return false;
368                 }
369             }
370 
371             return true;
372         }
373 
374         @SuppressWarnings("MethodWithMultipleLoops")
375         private boolean areParametersOfSameType(@NonNull String param1, @NonNull String param2) {
376             if (param1.equals(param2)) {
377                 return true;
378             }
379 
380             int i = -1;
381             char c;
382             do {
383                 i++;
384                 c = param1.charAt(i);
385             } while (c == '[');
386             if (c != 'T') {
387                 return false;
388             }
389 
390             String typeVarName1 = param1.substring(i);
391             String typeVarName2 = param2.substring(i);
392             String typeArg1 = null;
393 
394             for (Entry<String, String> typeParamAndArgName : typeParametersToTypeArgumentNames.entrySet()) {
395                 String typeMappingKey = typeParamAndArgName.getKey();
396                 String typeVarName = typeMappingKey.substring(typeMappingKey.indexOf(':') + 1);
397 
398                 if (typeVarName.equals(typeVarName1)) {
399                     typeArg1 = typeParamAndArgName.getValue();
400                     break;
401                 }
402             }
403 
404             return typeVarName2.equals(typeArg1);
405         }
406 
407         public boolean satisfiesSignature(@NonNull String otherSignature) {
408             GenericSignature other = new GenericSignature(otherSignature);
409             return other.areMatchingSignatures(this);
410         }
411     }
412 
413     @NonNull
414     public GenericSignature parseSignature(@NonNull String genericSignature) {
415         return new GenericSignature(genericSignature);
416     }
417 
418     @NonNull
419     public String resolveSignature(@NonNull String ownerTypeDesc, @NonNull String genericSignature) {
420         addTypeArgumentsIfAvailable(ownerTypeDesc, genericSignature);
421 
422         int p = genericSignature.lastIndexOf(')') + 1;
423         int q = genericSignature.length();
424         String returnType = genericSignature.substring(p, q);
425         String resolvedReturnType = replaceTypeParametersWithActualTypes(ownerTypeDesc, returnType);
426 
427         StringBuilder finalSignature = new StringBuilder(genericSignature);
428         finalSignature.replace(p, q, resolvedReturnType);
429         return finalSignature.toString();
430     }
431 
432     private void addTypeArgumentsIfAvailable(@NonNull String ownerTypeDesc, @NonNull String signature) {
433         int firstParen = signature.indexOf('(');
434         if (firstParen == 0) {
435             return;
436         }
437 
438         int p = 1;
439         boolean lastMappingFound = false;
440 
441         while (!lastMappingFound) {
442             int q = signature.indexOf(':', p);
443             String typeVar = signature.substring(p, q);
444 
445             q++;
446 
447             if (signature.charAt(q) == ':') {
448                 q++; // an unbounded type argument uses ":" as separator, while a bounded one uses "::"
449             }
450 
451             int r = signature.indexOf(':', q);
452 
453             if (r < 0) {
454                 r = firstParen - 2;
455                 lastMappingFound = true;
456             } else {
457                 r = signature.lastIndexOf(';', r);
458                 p = r + 1;
459             }
460 
461             String typeArg = signature.substring(q, r);
462             addTypeMapping(ownerTypeDesc, typeVar, typeArg);
463         }
464     }
465 
466     @NonNull
467     private String replaceTypeParametersWithActualTypes(@NonNull String ownerTypeDesc, @NonNull String typeDesc) {
468         if (typeDesc.charAt(0) == 'T' && !typeParametersToTypeArgumentNames.isEmpty()) {
469             return replaceTypeParameters(ownerTypeDesc, typeDesc);
470         }
471 
472         int p = typeDesc.indexOf('<');
473 
474         if (p < 0) {
475             return typeDesc;
476         }
477 
478         String resolvedTypeDesc = typeDesc;
479 
480         for (Entry<String, String> paramAndArg : typeParametersToTypeArgumentNames.entrySet()) {
481             String typeMappingKey = paramAndArg.getKey();
482             String typeParam = typeMappingKey.substring(typeMappingKey.indexOf(':') + 1) + ';';
483             String typeArg = paramAndArg.getValue() + ';';
484             resolvedTypeDesc = resolvedTypeDesc.replace(typeParam, typeArg);
485         }
486 
487         return resolvedTypeDesc;
488     }
489 
490     @NonNull
491     private String replaceTypeParameters(@NonNull String ownerTypeDesc, @NonNull String typeDesc) {
492         String typeParameter = typeDesc.substring(0, typeDesc.length() - 1);
493 
494         while (true) {
495             @Nullable
496             String typeArg = typeParametersToTypeArgumentNames.get(ownerTypeDesc + ':' + typeParameter);
497 
498             if (typeArg == null) {
499                 return typeDesc;
500             }
501 
502             if (typeArg.charAt(0) != 'T') {
503                 return typeArg + ';';
504             }
505 
506             typeParameter = typeArg;
507         }
508     }
509 
510     @NonNull
511     public Type resolveTypeVariable(@NonNull TypeVariable<?> typeVariable) {
512         String typeVarKey = getTypeVariableKey(typeVariable);
513         @Nullable
514         Type typeArgument = typeParametersToTypeArguments.get(typeVarKey);
515 
516         if (typeArgument == null) {
517             typeArgument = typeVariable.getBounds()[0];
518         }
519 
520         if (typeArgument instanceof TypeVariable<?>) {
521             typeArgument = resolveTypeVariable((TypeVariable<?>) typeArgument);
522         }
523 
524         return typeArgument;
525     }
526 
527     @NonNull
528     private static String getTypeVariableKey(@NonNull TypeVariable<?> typeVariable) {
529         String ownerClassDesc = getOwnerClassDesc(typeVariable);
530         return ownerClassDesc + ':' + typeVariable.getName();
531     }
532 
533     @NonNull
534     private static String getOwnerClassDesc(@NonNull TypeVariable<?> typeVariable) {
535         GenericDeclaration owner = typeVariable.getGenericDeclaration();
536         Class<?> ownerClass = owner instanceof Member ? ((Member) owner).getDeclaringClass() : (Class<?>) owner;
537         return getOwnerClassDesc(ownerClass);
538     }
539 
540     public boolean areMatchingTypes(@NonNull Type declarationType, @NonNull Type realizationType) {
541         if (declarationType.equals(realizationType)) {
542             return true;
543         }
544 
545         if (declarationType instanceof Class<?>) {
546             if (realizationType instanceof Class<?>) {
547                 return ((Class<?>) declarationType).isAssignableFrom((Class<?>) realizationType);
548             }
549         } else if (declarationType instanceof TypeVariable<?>) {
550             if (realizationType instanceof TypeVariable<?>) {
551                 return false;
552             }
553 
554             // noinspection RedundantIfStatement
555             if (areMatchingTypes((TypeVariable<?>) declarationType, realizationType)) {
556                 return true;
557             }
558         } else if (declarationType instanceof ParameterizedType) {
559             ParameterizedType parameterizedDeclarationType = (ParameterizedType) declarationType;
560             ParameterizedType parameterizedRealizationType = getParameterizedType(realizationType);
561 
562             if (parameterizedRealizationType != null) {
563                 return areMatchingTypes(parameterizedDeclarationType, parameterizedRealizationType);
564             }
565         }
566 
567         return false;
568     }
569 
570     @Nullable
571     private static ParameterizedType getParameterizedType(@NonNull Type realizationType) {
572         if (realizationType instanceof ParameterizedType) {
573             return (ParameterizedType) realizationType;
574         }
575 
576         if (realizationType instanceof Class<?>) {
577             return findRealizationSupertype((Class<?>) realizationType);
578         }
579 
580         return null;
581     }
582 
583     @Nullable
584     private static ParameterizedType findRealizationSupertype(@NonNull Class<?> realizationType) {
585         Type realizationSuperclass = realizationType.getGenericSuperclass();
586         ParameterizedType parameterizedRealizationType = null;
587 
588         if (realizationSuperclass instanceof ParameterizedType) {
589             parameterizedRealizationType = (ParameterizedType) realizationSuperclass;
590         } else {
591             for (Type realizationSupertype : realizationType.getGenericInterfaces()) {
592                 if (realizationSupertype instanceof ParameterizedType) {
593                     parameterizedRealizationType = (ParameterizedType) realizationSupertype;
594                     break;
595                 }
596             }
597         }
598 
599         return parameterizedRealizationType;
600     }
601 
602     private boolean areMatchingTypes(@NonNull TypeVariable<?> declarationType, @NonNull Type realizationType) {
603         String typeVarKey = getTypeVariableKey(declarationType);
604         @Nullable
605         Type resolvedType = typeParametersToTypeArguments.get(typeVarKey);
606 
607         return resolvedType != null && (resolvedType.equals(realizationType)
608                 || typeSatisfiesResolvedTypeVariable(resolvedType, realizationType));
609     }
610 
611     private boolean areMatchingTypes(@NonNull ParameterizedType declarationType,
612             @NonNull ParameterizedType realizationType) {
613         return declarationType.getRawType().equals(realizationType.getRawType())
614                 && haveMatchingActualTypeArguments(declarationType, realizationType);
615     }
616 
617     private boolean haveMatchingActualTypeArguments(@NonNull ParameterizedType declarationType,
618             @NonNull ParameterizedType realizationType) {
619         Type[] declaredTypeArguments = declarationType.getActualTypeArguments();
620         Type[] concreteTypeArguments = realizationType.getActualTypeArguments();
621 
622         for (int i = 0, n = declaredTypeArguments.length; i < n; i++) {
623             Type declaredTypeArg = declaredTypeArguments[i];
624             Type concreteTypeArg = concreteTypeArguments[i];
625 
626             if (declaredTypeArg instanceof TypeVariable<?>) {
627                 if (areMatchingTypeArguments((TypeVariable<?>) declaredTypeArg, concreteTypeArg)) {
628                     continue;
629                 }
630             } else if (areMatchingTypes(declaredTypeArg, concreteTypeArg)) {
631                 continue;
632             }
633 
634             return false;
635         }
636 
637         return true;
638     }
639 
640     @SuppressWarnings("RedundantIfStatement")
641     private boolean areMatchingTypeArguments(@NonNull TypeVariable<?> declaredType, @NonNull Type concreteType) {
642         String typeVarKey = getTypeVariableKey(declaredType);
643         @Nullable
644         Type resolvedType = typeParametersToTypeArguments.get(typeVarKey);
645 
646         if (resolvedType != null) {
647             if (resolvedType.equals(concreteType) || concreteType instanceof Class<?>
648                     && typeSatisfiesResolvedTypeVariable(resolvedType, (Class<?>) concreteType)) {
649                 return true;
650             }
651 
652             if (concreteType instanceof WildcardType
653                     && typeSatisfiesUpperBounds(resolvedType, ((WildcardType) concreteType).getUpperBounds())) {
654                 return true;
655             }
656         } else if (typeSatisfiesUpperBounds(concreteType, declaredType.getBounds())) {
657             return true;
658         }
659 
660         return false;
661     }
662 
663     private boolean typeSatisfiesResolvedTypeVariable(@NonNull Type resolvedType, @NonNull Type realizationType) {
664         Class<?> realizationClass = getClassType(realizationType);
665         return typeSatisfiesResolvedTypeVariable(resolvedType, realizationClass);
666     }
667 
668     private boolean typeSatisfiesResolvedTypeVariable(@NonNull Type resolvedType, @NonNull Class<?> realizationType) {
669         Class<?> resolvedClass = getClassType(resolvedType);
670         return resolvedClass.isAssignableFrom(realizationType);
671     }
672 
673     private boolean typeSatisfiesUpperBounds(@NonNull Type type, @NonNull Type[] upperBounds) {
674         Class<?> classType = getClassType(type);
675 
676         for (Type upperBound : upperBounds) {
677             Class<?> classBound = getClassType(upperBound);
678 
679             if (!classBound.isAssignableFrom(classType)) {
680                 return false;
681             }
682         }
683 
684         return true;
685     }
686 }