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 static java.lang.reflect.Modifier.isPrivate;
9   import static java.lang.reflect.Modifier.isStatic;
10  
11  import static mockit.internal.reflection.ParameterReflection.*;
12  
13  import edu.umd.cs.findbugs.annotations.NonNull;
14  import edu.umd.cs.findbugs.annotations.Nullable;
15  
16  import java.lang.reflect.InvocationTargetException;
17  import java.lang.reflect.Method;
18  import java.util.regex.Pattern;
19  
20  import mockit.Delegate;
21  import mockit.internal.util.StackTrace;
22  import mockit.internal.util.Utilities;
23  
24  public final class MethodReflection {
25      @NonNull
26      public static final Pattern JAVA_LANG = Pattern.compile("java.lang.", Pattern.LITERAL);
27  
28      private MethodReflection() {
29      }
30  
31      @Nullable
32      public static <T> T invoke(@NonNull Class<?> theClass, @Nullable Object targetInstance, @NonNull String methodName,
33              @NonNull Class<?>[] paramTypes, @NonNull Object... methodArgs) {
34          Method method = findSpecifiedMethod(theClass, methodName, paramTypes);
35          return invoke(targetInstance, method, methodArgs);
36      }
37  
38      @NonNull
39      private static Method findSpecifiedMethod(@NonNull Class<?> theClass, @NonNull String methodName,
40              @NonNull Class<?>[] paramTypes) {
41          while (true) {
42              Method declaredMethod = findSpecifiedMethodInGivenClass(theClass, methodName, paramTypes);
43  
44              if (declaredMethod != null) {
45                  return declaredMethod;
46              }
47  
48              Class<?> superClass = theClass.getSuperclass();
49  
50              if (superClass == null || superClass == Object.class) {
51                  String paramTypesDesc = getParameterTypesDescription(paramTypes);
52                  throw new IllegalArgumentException("Specified method not found: " + methodName + paramTypesDesc);
53              }
54  
55              // noinspection AssignmentToMethodParameter
56              theClass = superClass;
57          }
58      }
59  
60      @Nullable
61      private static Method findSpecifiedMethodInGivenClass(@NonNull Class<?> theClass, @NonNull String methodName,
62              @NonNull Class<?>[] paramTypes) {
63          for (Method declaredMethod : theClass.getDeclaredMethods()) {
64              if (declaredMethod.getName().equals(methodName)) {
65                  Class<?>[] declaredParameterTypes = declaredMethod.getParameterTypes();
66                  int firstRealParameter = indexOfFirstRealParameter(declaredParameterTypes, paramTypes);
67  
68                  if (firstRealParameter >= 0
69                          && matchesParameterTypes(declaredMethod.getParameterTypes(), paramTypes, firstRealParameter)) {
70                      return declaredMethod;
71                  }
72              }
73          }
74  
75          return null;
76      }
77  
78      @Nullable
79      public static <T> T invokePublicIfAvailable(@NonNull Class<?> aClass, @Nullable Object targetInstance,
80              @NonNull String methodName, @NonNull Class<?>[] parameterTypes, @NonNull Object... methodArgs) {
81          Method publicMethod;
82          try {
83              publicMethod = aClass.getMethod(methodName, parameterTypes);
84          } catch (NoSuchMethodException ignore) {
85              return null;
86          }
87  
88          return invoke(targetInstance, publicMethod, methodArgs);
89      }
90  
91      @Nullable
92      public static <T> T invokeWithCheckedThrows(@NonNull Class<?> theClass, @Nullable Object targetInstance,
93              @NonNull String methodName, @NonNull Class<?>[] paramTypes, @NonNull Object... methodArgs)
94              throws Throwable {
95          Method method = findSpecifiedMethod(theClass, methodName, paramTypes);
96          return invokeWithCheckedThrows(targetInstance, method, methodArgs);
97      }
98  
99      @Nullable
100     public static <T> T invoke(@Nullable Object targetInstance, @NonNull Method method, @NonNull Object... methodArgs) {
101         Utilities.ensureThatMemberIsAccessible(method);
102 
103         try {
104             // noinspection unchecked
105             return (T) method.invoke(targetInstance, methodArgs);
106         } catch (IllegalAccessException e) {
107             throw new RuntimeException(e);
108         } catch (IllegalArgumentException e) {
109             StackTrace.filterStackTrace(e);
110             throw new IllegalArgumentException("Failure to invoke method: " + method, e);
111         } catch (InvocationTargetException e) {
112             Throwable cause = e.getCause();
113 
114             if (cause instanceof Error) {
115                 throw (Error) cause;
116             } else if (cause instanceof RuntimeException) {
117                 throw (RuntimeException) cause;
118             } else {
119                 ThrowOfCheckedException.doThrow((Exception) cause);
120                 return null;
121             }
122         }
123     }
124 
125     @Nullable
126     public static <T> T invokeWithCheckedThrows(@Nullable Object targetInstance, @NonNull Method method,
127             @NonNull Object... methodArgs) throws Throwable {
128         Utilities.ensureThatMemberIsAccessible(method);
129 
130         try {
131             // noinspection unchecked
132             return (T) method.invoke(targetInstance, methodArgs);
133         } catch (IllegalArgumentException e) {
134             StackTrace.filterStackTrace(e);
135             throw new IllegalArgumentException("Failure to invoke method: " + method, e);
136         } catch (InvocationTargetException e) {
137             throw e.getCause();
138         }
139     }
140 
141     @Nullable
142     public static <T> T invoke(@NonNull Class<?> theClass, @Nullable Object targetInstance, @NonNull String methodName,
143             @NonNull Object... methodArgs) {
144         boolean staticMethod = targetInstance == null;
145         Class<?>[] argTypes = getArgumentTypesFromArgumentValues(methodArgs);
146         Method method = staticMethod ? findCompatibleStaticMethod(theClass, methodName, argTypes)
147                 : findCompatibleMethod(theClass, methodName, argTypes);
148 
149         if (staticMethod && !isStatic(method.getModifiers())) {
150             throw new IllegalArgumentException(
151                     "Attempted to invoke non-static method without an instance to invoke it on");
152         }
153 
154         T result = invoke(targetInstance, method, methodArgs);
155         return result;
156     }
157 
158     @NonNull
159     private static Method findCompatibleStaticMethod(@NonNull Class<?> theClass, @NonNull String methodName,
160             @NonNull Class<?>[] argTypes) {
161         Method methodFound = findCompatibleMethodInClass(theClass, methodName, argTypes);
162 
163         if (methodFound != null) {
164             return methodFound;
165         }
166 
167         String argTypesDesc = getParameterTypesDescription(argTypes);
168         throw new IllegalArgumentException("No compatible static method found: " + methodName + argTypesDesc);
169     }
170 
171     @NonNull
172     public static Method findCompatibleMethod(@NonNull Class<?> theClass, @NonNull String methodName,
173             @NonNull Class<?>[] argTypes) {
174         Method methodFound = findCompatibleMethodIfAvailable(theClass, methodName, argTypes);
175 
176         if (methodFound != null) {
177             return methodFound;
178         }
179 
180         String argTypesDesc = getParameterTypesDescription(argTypes);
181         throw new IllegalArgumentException("No compatible method found: " + methodName + argTypesDesc);
182     }
183 
184     @Nullable
185     private static Method findCompatibleMethodIfAvailable(@NonNull Class<?> theClass, @NonNull String methodName,
186             @NonNull Class<?>[] argTypes) {
187         Method methodFound = null;
188 
189         while (true) {
190             Method compatibleMethod = findCompatibleMethodInClass(theClass, methodName, argTypes);
191 
192             if (compatibleMethod != null && (methodFound == null
193                     || hasMoreSpecificTypes(compatibleMethod.getParameterTypes(), methodFound.getParameterTypes()))) {
194                 methodFound = compatibleMethod;
195             }
196 
197             Class<?> superClass = theClass.getSuperclass();
198 
199             if (superClass == null || superClass == Object.class) {
200                 break;
201             }
202 
203             // noinspection AssignmentToMethodParameter
204             theClass = superClass;
205         }
206 
207         return methodFound;
208     }
209 
210     @Nullable
211     private static Method findCompatibleMethodInClass(@NonNull Class<?> theClass, @NonNull String methodName,
212             @NonNull Class<?>[] argTypes) {
213         Method found = null;
214         Class<?>[] foundParamTypes = null;
215 
216         for (Method declaredMethod : theClass.getDeclaredMethods()) {
217             if (declaredMethod.getName().equals(methodName)) {
218                 Class<?>[] declaredParamTypes = declaredMethod.getParameterTypes();
219                 int firstRealParameter = indexOfFirstRealParameter(declaredParamTypes, argTypes);
220 
221                 if (firstRealParameter >= 0
222                         && (matchesParameterTypes(declaredParamTypes, argTypes, firstRealParameter)
223                                 || acceptsArgumentTypes(declaredParamTypes, argTypes, firstRealParameter))
224                         && (foundParamTypes == null || hasMoreSpecificTypes(declaredParamTypes, foundParamTypes))) {
225                     found = declaredMethod;
226                     foundParamTypes = declaredParamTypes;
227                 }
228             }
229         }
230 
231         return found;
232     }
233 
234     @NonNull
235     public static Method findNonPrivateHandlerMethod(@NonNull Object handler) {
236         Class<?> handlerClass = handler.getClass();
237         Method nonPrivateMethod;
238 
239         do {
240             nonPrivateMethod = findNonPrivateHandlerMethod(handlerClass);
241 
242             if (nonPrivateMethod != null) {
243                 break;
244             }
245 
246             handlerClass = handlerClass.getSuperclass();
247         } while (handlerClass != null && handlerClass != Object.class);
248 
249         if (nonPrivateMethod == null) {
250             throw new IllegalArgumentException("No non-private instance method found");
251         }
252 
253         return nonPrivateMethod;
254     }
255 
256     @Nullable
257     private static Method findNonPrivateHandlerMethod(@NonNull Class<?> handlerClass) {
258         Method[] declaredMethods = handlerClass.getDeclaredMethods();
259         Method found = null;
260 
261         for (Method declaredMethod : declaredMethods) {
262             int methodModifiers = declaredMethod.getModifiers();
263 
264             if (!isPrivate(methodModifiers) && !isStatic(methodModifiers)) {
265                 if (found != null) {
266                     String methodType = Delegate.class.isAssignableFrom(handlerClass) ? "delegate"
267                             : "invocation handler";
268                     throw new IllegalArgumentException("More than one candidate " + methodType + " method found: "
269                             + methodSignature(found) + ", " + methodSignature(declaredMethod));
270                 }
271 
272                 found = declaredMethod;
273             }
274         }
275 
276         return found;
277     }
278 
279     @NonNull
280     private static String methodSignature(@NonNull Method method) {
281         String signature = JAVA_LANG.matcher(method.toGenericString()).replaceAll("");
282         int p = signature.lastIndexOf('(');
283         int q = signature.lastIndexOf('.', p);
284 
285         return signature.substring(q + 1);
286     }
287 }