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