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 io.earcam.instrumental.reflect.Matchers.aStream;
22 import static io.earcam.instrumental.reflect.Matchers.isAbsent;
23 import static io.earcam.instrumental.reflect.Matchers.isPresent;
24 import static java.util.Arrays.stream;
25 import static java.util.stream.Collectors.joining;
26 import static java.util.stream.Collectors.toList;
27 import static org.hamcrest.MatcherAssert.assertThat;
28 import static org.hamcrest.Matchers.allOf;
29 import static org.hamcrest.Matchers.arrayContaining;
30 import static org.hamcrest.Matchers.containsInAnyOrder;
31 import static org.hamcrest.Matchers.equalTo;
32 import static org.hamcrest.Matchers.hasItem;
33 import static org.hamcrest.Matchers.hasProperty;
34 import static org.hamcrest.Matchers.is;
35 import static org.hamcrest.Matchers.not;
36 import static org.junit.jupiter.api.Assertions.assertFalse;
37 import static org.junit.jupiter.api.Assertions.assertTrue;
38
39 import java.io.Serializable;
40 import java.lang.invoke.MethodHandle;
41 import java.lang.reflect.Method;
42 import java.util.Arrays;
43 import java.util.List;
44 import java.util.Optional;
45
46 import org.junit.jupiter.api.BeforeAll;
47 import org.junit.jupiter.api.Test;
48
49
50
51
52
53
54
55
56
57
58 public class MethodsTest {
59
60 @Override
61 protected void finalize() throws Throwable
62 {
63 super.finalize();
64 }
65
66 private int onlyHereToInduceBridge = 42;
67
68 public class InnerToInduceBridge {
69 public int nabOuterField()
70 {
71 return onlyHereToInduceBridge;
72 }
73
74 }
75
76 public static class OuterClassToInduceBridge {
77
78 private int numb = 1;
79
80 public class Inner {
81 private int num = 5;
82
83
84 public int nabOuterField()
85 {
86 return numb;
87 }
88 }
89
90
91 public int nabInnerField()
92 {
93 return new Inner().num;
94 }
95 }
96
97
98 @SuppressWarnings("unchecked")
99 @Test
100 public void getMethodReturnsSynthetic()
101 {
102 Optional<Method> method = Methods.allMethodsOf(OuterClassToInduceBridge.Inner.class)
103 .filter(Methods.IS_SYNTHETIC)
104 .filter(m -> m.getReturnType().equals(int.class))
105
106 .findAny();
107
108 assertThat(method, isPresent());
109 assertThat(method.get().getReturnType(), is(int.class));
110 assertThat(method.get().getParameterTypes(), is(arrayContaining(OuterClassToInduceBridge.Inner.class)));
111 }
112
113 public static class Super {
114 public void foo()
115 {}
116
117
118 protected void bar()
119 {}
120 }
121
122 public static class Sub extends Super {
123 @Override
124 public void foo()
125 {}
126 }
127
128
129 @Test
130 public void getMethodReturnsOverriddenFromSubclass()
131 {
132 Method method = Methods.getMethod(Sub.class, "foo").get();
133 assertThat(method.getDeclaringClass(), is(Sub.class));
134 }
135
136
137 @Test
138 public void getMethodReturnsInherited()
139 {
140 Method method = Methods.getMethod(Sub.class, "bar").get();
141 assertThat(method.getDeclaringClass(), is(Super.class));
142 }
143
144
145 @Test
146 public void getMethodReturnsEmptyOptional()
147 {
148 assertThat(Methods.getMethod(Sub.class, "humbug").isPresent(), is(false));
149 }
150
151
152
153 @SuppressWarnings("unused")
154 private class BaseClass {
155
156 protected void boo(String goose)
157 {}
158 }
159
160 @SuppressWarnings("unused")
161 private class SubClass extends BaseClass {
162 @Override
163 protected void boo(String goose)
164 {}
165
166
167 public String cheese()
168 {
169 return "red leicester";
170 }
171 }
172
173 @SuppressWarnings("unused")
174 private class SubOfSubClass extends SubClass {
175
176 public String boo(String... geese)
177 {
178 return "Boo, boo I say to you geese...\n" +
179 stream(geese).map(g -> "Boo to " + g).collect(joining("\n"));
180 }
181 }
182
183 private static Method BASE_BOO_METHOD;
184 private static Method OVERRIDDING_BOO_METHOD;
185
186
187 @BeforeAll
188 public static void setUp() throws NoSuchMethodException, SecurityException
189 {
190 BASE_BOO_METHOD = BaseClass.class.getDeclaredMethod("boo", String.class);
191 OVERRIDDING_BOO_METHOD = SubClass.class.getDeclaredMethod("boo", String.class);
192 }
193
194
195 @Test
196 public void doesNotIncludeMethodsFromJavaLangObject()
197 {
198 assertThat(
199 Methods.methodsOf(BaseClass.class)
200 .filter(m -> m.getDeclaringClass() == Object.class)
201 .findAny(),
202 isAbsent());
203
204 assertThat(Methods.methodsOf(BaseClass.class), aStream(not(hasItem(hasProperty("declaringClass", equalTo(Object.class))))));
205 }
206
207
208 @Test
209 public void testFindsSingleDeclaredProtectedMethod()
210 {
211 assertThat(Methods.methodsOf(BaseClass.class), aStream(hasItem(BASE_BOO_METHOD)));
212 }
213
214
215 @Test
216 public void testFindsCorrectlyOverriddenMethod()
217 {
218 assertThat(Methods.methodsOf(SubClass.class).collect(toList()), allOf(
219 hasItem(OVERRIDDING_BOO_METHOD),
220 not(hasItem(BASE_BOO_METHOD))));
221 }
222
223
224 @Test
225 public void testFindsCorrectlyOverriddenAndInheritedMethod()
226 {
227 assertThat(Methods.methodsOf(SubOfSubClass.class).collect(toList()), hasItem(OVERRIDDING_BOO_METHOD));
228 }
229
230
231 @Test
232 public void testFindsAllMethodsDeclaredOrInherited()
233 {
234 assertThat(Methods.methodsOf(SubOfSubClass.class).collect(toList()), hasItem(OVERRIDDING_BOO_METHOD));
235 }
236
237
238 @Test
239 public void givenExistingMethodThenOptionalIsPresent()
240 {
241 Optional<Method> declaredMethod = Methods.getMethod(BaseClass.class, "boo", String.class);
242 assertTrue(declaredMethod.isPresent());
243 assertThat(declaredMethod.get().getName(), is("boo"));
244 }
245
246
247 @Test
248 public void givenNonExistentMethodThenOptionalIsAbsent()
249 {
250 assertFalse(Methods.getMethod(BaseClass.class, "nonexistent", String.class).isPresent());
251 }
252
253 interface A<T extends Serializable> {
254 void doSome(T thing);
255 }
256
257 class B implements A<String> {
258 @Override
259 public void doSome(String thing)
260 {}
261 }
262
263
264 @Test
265 public void whenGenericInterfacePresentThenOnlyListConcrete()
266 {
267 assertThat(Methods.methodsOf(B.class)
268 .filter(m -> "doSome".equals(m.getName()))
269 .filter(m -> String.class.equals(m.getParameterTypes()[0]))
270 .findAny(), isPresent());
271 }
272
273 public static class W<N extends Number> {
274 public void receive(N number)
275 {}
276 }
277
278 class X extends W<Integer> {
279 int x;
280
281
282 @Override
283 public void receive(Integer number)
284 {
285 x = number;
286 }
287 }
288
289 public static class Y extends W<Float> {
290 float y;
291
292
293 @Override
294 public void receive(Float number)
295 {
296 y = number;
297 }
298
299
300 public void randomUnrelatedMethod(String s1)
301 {}
302 }
303
304 public static class Z extends Y {
305 float z;
306
307
308 @Override
309 public void receive(Float number)
310 {
311 z = number;
312 }
313
314
315 public void randomUnrelatedMethod(String s1, String s2)
316 {}
317 }
318
319
320 @Test
321 public void whenGenericSuperClassPresentForNestedInnerClassThenOnlyListConcrete()
322 {
323 assertThat(Methods.methodsOf(X.class)
324 .filter(m -> "receive".equals(m.getName()))
325 .count(), is(equalTo(1L)));
326 }
327
328
329 @Test
330 public void whenGenericSuperClassPresentForStaticInnerClassThenOnlyListConcrete()
331 {
332 assertThat(Methods.methodsOf(Y.class)
333 .filter(m -> "receive".equals(m.getName()))
334 .count(), is(equalTo(1L)));
335 }
336
337
338 @Test
339 public void testWhenPrivateFieldOnInnerClassIsReferencedThenSyntheticMethodShouldNotBeRetrieved()
340 {
341 @SuppressWarnings("unused")
342 class IntegerEventSubscriber {
343 private Integer event;
344
345
346 public void on(Integer event)
347 {
348 this.event = event;
349 }
350 }
351 IntegerEventSubscriber subscriber = new IntegerEventSubscriber();
352
353 subscriber.event = 101;
354
355 assertThat("Direct field access of an inner class' private field trigger the creation of a synthetic method",
356 Arrays.stream(subscriber.getClass().getDeclaredMethods()).filter(Method::isSynthetic).findAny().isPresent(), is(true));
357
358 assertThat(Methods.methodsOf(IntegerEventSubscriber.class).count(), is(1L));
359
360 }
361
362
363 @Test
364 public void whenBlah()
365 {
366 assertThat(Methods.methodsOf(Z.class)
367 .filter(m -> "receive".equals(m.getName()))
368 .count(), is(equalTo(1L)));
369 }
370
371
372 @Test
373 public void whenGenericInterfacePresentForClassThenOnlyListConcrete()
374 {
375 assertThat(Methods.methodsOf(String.CASE_INSENSITIVE_ORDER.getClass())
376 .filter(m -> "compare".equals(m.getName()))
377 .count(), is(equalTo(1L)));
378 }
379
380 public abstract class TypeCapture<T> {
381 public abstract void withType(T type);
382 }
383
384
385
386
387 @Test
388 public void testGenericType()
389 {
390 TypeCapture<W<Integer>> tc = new TypeCapture<W<Integer>>() {
391 @Override
392 public void withType(W<Integer> type)
393 {}
394 };
395 assertThat(Methods.methodsOf(tc.getClass())
396 .filter(m -> "withType".equals(m.getName()))
397 .count(), is(equalTo(1L)));
398 }
399
400 public abstract class SubTypeCapture<T extends Number> extends TypeCapture<W<T>> {
401 public abstract void withType(W<T> type);
402
403
404 public abstract void withType(W<Float> type, Object otherParameter);
405 }
406
407
408 @Test
409 public void testGenericType2()
410 {
411 Method overridden = Methods.getMethod(SubTypeCapture.class, "withType", W.class).get();
412 Method overloaded = Methods.getMethod(SubTypeCapture.class, "withType", W.class, Object.class).get();
413
414 List<Method> methods = Methods.methodsOf(SubTypeCapture.class)
415 .filter(m -> "withType".equals(m.getName()))
416 .collect(toList());
417
418 assertThat(methods, containsInAnyOrder(overridden, overloaded));
419 }
420
421 interface Foo {
422 default void defaultMethod(String arg)
423 {}
424 }
425
426 class Bar implements Foo {
427 @Override
428 public void defaultMethod(String arg)
429 {}
430 }
431
432 class Bar2 implements Foo {}
433
434 class Bar3 extends Bar2 {}
435
436
437 @Test
438 public void methodsOfDoesNotReturnOverridenDefault()
439 {
440 Method method = Methods.methodsOf(Bar.class)
441 .filter(m -> m.getName().equals("defaultMethod"))
442 .findAny().get();
443
444 assertThat(method.getDeclaringClass(), is(Bar.class));
445 }
446
447
448 @Test
449 public void methodsOfReturnsDefault()
450 {
451 Method method = Methods.methodsOf(Bar2.class)
452 .filter(m -> m.getName().equals("defaultMethod"))
453 .findAny().get();
454
455 assertThat(method.getDeclaringClass(), is(Foo.class));
456 }
457
458
459 @Test
460 public void methodsOfReturnsInheritedDefault()
461 {
462 Method method = Methods.methodsOf(Bar3.class)
463 .filter(m -> m.getName().equals("defaultMethod"))
464 .findAny().get();
465
466 assertThat(method.getDeclaringClass(), is(Foo.class));
467 }
468
469 public static interface WithDefault {
470 default String hello(String name)
471 {
472 return "Hello " + name;
473 }
474 }
475
476 public static class ImplementingWithDefault implements WithDefault {
477
478 }
479
480
481 @Test
482 public void handleForDefaultMethod() throws Throwable
483 {
484 Method method = Methods.getMethod(ImplementingWithDefault.class, "hello", String.class).get();
485
486 ImplementingWithDefault target = new ImplementingWithDefault();
487 String arg = "name";
488
489 MethodHandle handle = Methods.handleFor(method);
490
491 assertThat(handle.bindTo(target).invoke(arg), is(equalTo(target.hello(arg))));
492 }
493
494
495
496 public String taDa()
497 {
498 return "TA-DA";
499 }
500
501
502 @Test
503 public void handleForNormalMethod() throws Throwable
504 {
505 Method method = Methods.getMethod(MethodsTest.class, "taDa")
506 .orElseThrow(NullPointerException::new);
507
508 MethodHandle handle = Methods.handleFor(method).bindTo(this);
509
510 assertThat(handle.invoke(), is(equalTo(taDa())));
511 }
512
513
514
515 public static String baddaBing()
516 {
517 return "Badda Bing";
518 }
519
520
521 @Test
522 public void handleForStaticMethod() throws Throwable
523 {
524 Method method = Methods.getMethod(MethodsTest.class, "baddaBing").get();
525 MethodHandle handle = Methods.handleFor(method);
526
527 assertThat(handle.invoke(), is(equalTo(baddaBing())));
528 }
529
530
531 @Override
532 public int hashCode()
533 {
534 return super.hashCode();
535 }
536
537
538 @Test
539 public void handleForOverridenMethod() throws Throwable
540 {
541 Method method = Methods.getMethod(MethodsTest.class, "hashCode").get();
542 MethodHandle handle = Methods.handleFor(method).bindTo(this);
543
544 assertThat(handle.invoke(), is(equalTo(hashCode())));
545 }
546
547
548 @Test
549 public void handleForInheritedMethod() throws Throwable
550 {
551 Method method = Methods.getMethod(MethodsTest.class, "equals", Object.class).get();
552 MethodHandle handle = Methods.handleFor(method).bindTo(this);
553
554 assertThat(handle.invokeWithArguments(this), is(true));
555 assertThat(handle.invokeWithArguments(new Object[] { null }), is(false));
556 }
557 }