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.injection;
7   
8   import static mockit.internal.reflection.MethodReflection.invoke;
9   import static mockit.internal.util.Utilities.getClassType;
10  
11  import edu.umd.cs.findbugs.annotations.NonNull;
12  import edu.umd.cs.findbugs.annotations.Nullable;
13  
14  import java.lang.reflect.Method;
15  import java.lang.reflect.ParameterizedType;
16  import java.lang.reflect.Type;
17  import java.lang.reflect.WildcardType;
18  import java.util.Map.Entry;
19  import java.util.NavigableMap;
20  import java.util.TreeMap;
21  
22  final class InterfaceResolution {
23      @NonNull
24      private final NavigableMap<ParameterizedType, Method> interfaceResolutionMethods;
25  
26      InterfaceResolution() {
27          interfaceResolutionMethods = new TreeMap<>((t1, t2) -> {
28              if (t1 == t2) {
29                  return 0;
30              }
31  
32              Type targetType1 = t1.getActualTypeArguments()[0];
33              Type targetType2 = t2.getActualTypeArguments()[0];
34  
35              if (targetType1 == targetType2) {
36                  return 0;
37              }
38  
39              if (targetType1 instanceof WildcardType) {
40                  if (targetType2 instanceof WildcardType) {
41                      return compareTypesFromResolutionMethods((WildcardType) targetType1, (WildcardType) targetType2);
42                  }
43  
44                  return 1;
45              }
46  
47              return -1;
48          });
49      }
50  
51      private static int compareTypesFromResolutionMethods(@NonNull WildcardType type1, @NonNull WildcardType type2) {
52          Type upperBound1 = type1.getUpperBounds()[0];
53          Class<?> classOfUpperBound1 = getClassType(upperBound1);
54  
55          Type upperBound2 = type2.getUpperBounds()[0];
56          Class<?> classOfUpperBound2 = getClassType(upperBound2);
57  
58          if (classOfUpperBound1.isAssignableFrom(classOfUpperBound2)) {
59              return 1;
60          }
61  
62          if (classOfUpperBound2.isAssignableFrom(classOfUpperBound1)) {
63              return -1;
64          }
65  
66          return classOfUpperBound1.getName().compareTo(classOfUpperBound2.getName());
67      }
68  
69      boolean canResolveInterfaces() {
70          return !interfaceResolutionMethods.isEmpty();
71      }
72  
73      void addInterfaceResolutionMethod(@NonNull ParameterizedType interfaceType, @NonNull Method resolutionMethod) {
74          interfaceResolutionMethods.put(interfaceType, resolutionMethod);
75      }
76  
77      @Nullable
78      Class<?> resolveInterface(@NonNull Class<?> anInterface, @NonNull Object testClassInstance) {
79          if (interfaceResolutionMethods.isEmpty()) {
80              return null;
81          }
82  
83          for (Entry<ParameterizedType, Method> typeAndMethod : interfaceResolutionMethods.entrySet()) {
84              ParameterizedType acceptedType = typeAndMethod.getKey();
85              Method method = typeAndMethod.getValue();
86              Type targetType = acceptedType.getActualTypeArguments()[0];
87  
88              if (targetType == anInterface || targetType instanceof WildcardType
89                      && satisfiesUpperBounds(anInterface, (WildcardType) targetType)) {
90                  Class<?> implementationClass = invoke(testClassInstance, method, anInterface);
91  
92                  if (implementationClass != null) {
93                      return implementationClass;
94                  }
95              }
96          }
97  
98          return null;
99      }
100 
101     private static boolean satisfiesUpperBounds(@NonNull Class<?> interfaceType, @NonNull WildcardType targetType) {
102         for (Type upperBound : targetType.getUpperBounds()) {
103             Class<?> classOfUpperBound = getClassType(upperBound);
104 
105             if (!classOfUpperBound.isAssignableFrom(interfaceType)) {
106                 return false;
107             }
108         }
109 
110         return true;
111     }
112 }