1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package io.earcam.instrumental.reflect;
20
21 import static java.util.stream.Collectors.toList;
22
23 import java.lang.invoke.MethodHandle;
24 import java.lang.invoke.MethodHandles;
25 import java.lang.invoke.MethodHandles.Lookup;
26 import java.lang.invoke.MethodType;
27 import java.lang.reflect.Field;
28 import java.lang.reflect.Method;
29 import java.lang.reflect.Modifier;
30 import java.lang.reflect.ParameterizedType;
31 import java.lang.reflect.Type;
32 import java.util.ArrayList;
33 import java.util.Arrays;
34 import java.util.List;
35 import java.util.Objects;
36 import java.util.Optional;
37 import java.util.function.Predicate;
38 import java.util.stream.Stream;
39
40 import javax.annotation.ParametersAreNonnullByDefault;
41
42 import io.earcam.unexceptional.Exceptional;
43
44
45
46
47
48
49
50 @ParametersAreNonnullByDefault
51 public final class Methods {
52
53
54 public static final Predicate<Method> IS_BRIDGE = Method::isBridge;
55
56 public static final Predicate<Method> IS_SYNTHETIC = Method::isSynthetic;
57
58 public static final Predicate<Method> IS_STATIC = hasModifiers(Modifier.STATIC);
59
60 static final Predicate<Method> IS_NOT_BRIDGE = IS_BRIDGE.negate();
61 static final Predicate<Method> IS_NOT_SYNTHETIC = IS_SYNTHETIC.negate();
62 static final Predicate<Method> IS_NOT_STATIC = IS_STATIC.negate();
63
64
65 private Methods()
66 {}
67
68
69
70
71
72
73
74
75
76
77 public static Predicate<Method> hasModifiers(int... modifiers)
78 {
79 int mods = Arrays.stream(modifiers).reduce(0, (a, b) -> a | b);
80 return m -> (m.getModifiers() & mods) == mods;
81 }
82
83
84
85
86
87
88
89
90
91
92
93
94 public static Optional<Method> getMethod(Class<?> type, String name, Class<?>... parameterTypes)
95 {
96 return allMethodsOf(type)
97 .filter(m -> m.getName().equals(name) && Arrays.equals(m.getParameterTypes(), parameterTypes))
98 .findFirst();
99 }
100
101
102
103
104
105
106
107
108
109
110 public static Optional<Method> getMethod(Object instance, String name, Object... args)
111 {
112 return getMethod(instance.getClass(), name, parameterTypesOf(args));
113 }
114
115
116 private static Class<?>[] parameterTypesOf(Object[] args)
117 {
118 return Arrays.stream(args)
119 .map(Object::getClass)
120 .toArray(s -> new Class<?>[s]);
121 }
122
123
124
125
126
127
128
129
130
131
132
133 public static Stream<Method> allMethodsOf(Class<?> type)
134 {
135 Objects.requireNonNull(type, "type cannot be null");
136 Stream<Method> methods = Stream.empty();
137 Class<?> t = type;
138 while(t != null) {
139 methods = Stream.concat(methods, Arrays.stream(t.getDeclaredMethods())).sequential();
140 t = t.getSuperclass();
141 }
142 t = type;
143 while(t != null) {
144 for(Class<?> iface : t.getInterfaces()) {
145 methods = Stream.concat(methods, Arrays.stream(iface.getDeclaredMethods()).filter(Method::isDefault)).sequential();
146 }
147 t = t.getSuperclass();
148 }
149 return methods;
150 }
151
152
153
154
155
156
157
158
159
160
161
162
163 public static Stream<Method> methodsOf(Class<?> type)
164 {
165 List<Method> methods = allMethodsOf(type)
166 .filter(IS_NOT_BRIDGE.and(IS_NOT_SYNTHETIC))
167 .filter(Methods::isNotDeclaredOnObject)
168 .collect(toList());
169
170 List<Method> overridenRidden = removeOverridden(methods);
171 return overridenRidden.stream().sequential();
172 }
173
174
175
176
177
178
179
180
181
182
183 protected static List<Method> removeOverridden(List<Method> methods)
184 {
185 List<Method> overridenRidden = new ArrayList<>(methods.size());
186 for(Method method : methods) {
187 if(!isOverridden(method, overridenRidden)) {
188 overridenRidden.add(method);
189 }
190 }
191 return overridenRidden;
192 }
193
194
195 private static boolean isNotDeclaredOnObject(Method m)
196 {
197 return m.getDeclaringClass() != Object.class;
198 }
199
200
201 private static boolean isOverridden(Method method, List<Method> overridenRidden)
202 {
203 Predicate<Method> bySameName = m -> m.getName().equals(method.getName());
204 Predicate<Method> parameterTypes = sameParameters(method);
205 Predicate<Method> returnTypes = m -> m.getReturnType().equals(method.getReturnType());
206
207 return overridenRidden.stream()
208 .anyMatch(bySameName.and(parameterTypes).and(returnTypes));
209 }
210
211
212 private static Predicate<Method> sameParameters(Method method)
213 {
214 Predicate<Method> numberOfParameters = m -> m.getParameterCount() == method.getParameterCount();
215 Predicate<Method> parameterTypes = m -> Arrays.equals(m.getParameterTypes(), method.getParameterTypes());
216
217 Predicate<Method> genericParameterTypes = m -> {
218 Type[] genericTypes = m.getGenericParameterTypes();
219 for(int i = 0; i < m.getParameterCount(); i++) {
220 Class<?> type = getClassFromType(genericTypes[i], m.getParameterTypes()[i], m);
221 if(!method.getParameterTypes()[i].isAssignableFrom(type)) {
222 return false;
223 }
224 }
225 return true;
226 };
227 return numberOfParameters.and(parameterTypes.or(genericParameterTypes));
228 }
229
230
231 private static Class<?> getClassFromType(Type type, Class<?> clazz, Method m)
232 {
233 if(type instanceof ParameterizedType) {
234 ParameterizedType parameterized = (ParameterizedType) type;
235 return Exceptional.apply(classLoaderOf(m)::loadClass, parameterized.getRawType().getTypeName());
236 }
237 if(type.getTypeName().equals(clazz.getCanonicalName())) {
238 return clazz;
239 }
240 return Exceptional.apply(classLoaderOf(m)::loadClass, type.getTypeName());
241 }
242
243
244 private static ClassLoader classLoaderOf(Method m)
245 {
246 ClassLoader classLoader = m.getDeclaringClass().getClassLoader();
247 return (classLoader == null) ? ClassLoader.getSystemClassLoader() : classLoader;
248 }
249
250
251
252
253
254
255
256
257 public static MethodHandle handleFor(Method method)
258 {
259 return Exceptional.get(() -> isJava8() ? java8HandleFor(method) : java9PlusHandleFor(method));
260 }
261
262
263 private static boolean isJava8()
264 {
265 return System.getProperty("java.version").startsWith("1.8");
266 }
267
268
269 private static MethodHandle java9PlusHandleFor(Method method) throws ReflectiveOperationException
270 {
271 Class<?> targetClass = method.getDeclaringClass();
272 String descriptor = Names.descriptorFor(method).toString();
273 MethodType methodType = MethodType.fromMethodDescriptorString(descriptor, targetClass.getClassLoader());
274
275 if(IS_STATIC.test(method)) {
276 return MethodHandles.lookup()
277 .in(Methods.class)
278 .findStatic(targetClass, method.getName(), methodType);
279 } else if(method.isDefault()) {
280 return MethodHandles.lookup()
281 .in(Methods.class)
282 .findSpecial(targetClass, method.getName(), methodType, targetClass);
283 } else {
284 return MethodHandles.lookup()
285 .in(Methods.class)
286 .findVirtual(targetClass, method.getName(), methodType);
287 }
288
289 }
290
291
292 private static MethodHandle java8HandleFor(Method method) throws ReflectiveOperationException
293 {
294 Class<?> targetClass = method.getDeclaringClass();
295 Field field = Lookup.class.getDeclaredField("IMPL_LOOKUP");
296 field.setAccessible(true);
297 Lookup lookup = (Lookup) field.get(null);
298 return (IS_STATIC.test(method)) ? lookup.unreflect(method) : lookup.unreflectSpecial(method, targetClass);
299 }
300 }