View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.juneau.reflect;
18  
19  import static java.lang.annotation.ElementType.*;
20  import static java.lang.annotation.RetentionPolicy.*;
21  import static org.apache.juneau.Context.*;
22  import static org.junit.jupiter.api.Assertions.*;
23  
24  import java.lang.annotation.*;
25  import java.lang.reflect.*;
26  import java.util.*;
27  import java.util.function.*;
28  import java.util.stream.*;
29  
30  import org.apache.juneau.*;
31  import org.apache.juneau.annotation.*;
32  import org.apache.juneau.svl.*;
33  import org.junit.jupiter.api.*;
34  
35  class MethodInfo_Test extends TestBase {
36  
37  	@Documented
38  	@Target({METHOD,TYPE})
39  	@Retention(RUNTIME)
40  	@Inherited
41  	public static @interface A {
42  		String value();
43  	}
44  
45  	@Documented
46  	@Target({METHOD,TYPE})
47  	@Retention(RUNTIME)
48  	@Inherited
49  	public static @interface AX {
50  		String value();
51  	}
52  
53  	@Documented
54  	@Target({METHOD,TYPE})
55  	@Retention(RUNTIME)
56  	@Inherited
57  	@ContextApply(AConfigApply.class)
58  	public static @interface AConfig {
59  		String value();
60  	}
61  
62  	public static class AConfigApply extends AnnotationApplier<AConfig,Context.Builder> {
63  		protected AConfigApply(VarResolverSession vr) {
64  			super(AConfig.class, Context.Builder.class, vr);
65  		}
66  		@Override
67  		public void apply(AnnotationInfo<AConfig> ai, Context.Builder b) {}  // NOSONAR
68  	}
69  
70  	private static void check(String expected, Object o) {
71  		assertEquals(expected, TO_STRING.apply(o));
72  	}
73  
74  	private static final Function<Object,String> TO_STRING = new Function<>() {
75  		@Override
76  		public String apply(Object t) {
77  			if (t == null)
78  				return null;
79  			if (t instanceof MethodInfo t2)
80  				return t2.getDeclaringClass().getSimpleName() + '.' + ((MethodInfo)t).getShortName();
81  			if (t instanceof Method t2)
82  				return t2.getDeclaringClass().getSimpleName() + '.' + MethodInfo.of((Method)t).getShortName();
83  			if (t instanceof List<?> t2)
84  				return (t2.stream().map(this).collect(Collectors.joining(",")));
85  			if (t instanceof AnnotationInfo t2)
86  				return apply(t2.inner());
87  			if (t instanceof A t2)
88  				return "@A(" + t2.value() + ")";
89  			if (t instanceof PA t2)
90  				return "@PA(" + t2.value() + ")";
91  			if (t instanceof AConfig t2)
92  				return "@AConfig(" + t2.value() + ")";
93  			if (t instanceof AnnotationList t2)
94  				return t2.toString();
95  			if (t instanceof ClassInfo t2)
96  				return t2.getSimpleName();
97  			return t.toString();
98  		}
99  	};
100 
101 	private static MethodInfo ofm(Class<?> c, String name, Class<?>...pt) {
102 		try {
103 			return MethodInfo.of(c.getDeclaredMethod(name, pt));
104 		} catch (NoSuchMethodException | SecurityException e) {
105 			fail(e.getLocalizedMessage());
106 		}
107 		return null;
108 	}
109 
110 	//-----------------------------------------------------------------------------------------------------------------
111 	// Instantiation.
112 	//-----------------------------------------------------------------------------------------------------------------
113 
114 	public static class A1 {
115 		public void m() {}  // NOSONAR
116 	}
117 	static MethodInfo a_m = ofm(A1.class, "m");  // NOSONAR
118 
119 	@Test void of_withDeclaringClass() {
120 		check("A1.m()", a_m);
121 		check("A1.m()", MethodInfo.of(ClassInfo.of(A1.class), a_m.inner()));
122 	}
123 
124 	@Test void of_withoutDeclaringClass() {
125 		var mi = MethodInfo.of(a_m.inner());
126 		check("A1.m()", mi);
127 	}
128 
129 	@Test void of_null() {
130 		check(null, MethodInfo.of(null));
131 		check(null, MethodInfo.of((ClassInfo)null, null));
132 	}
133 
134 	//-----------------------------------------------------------------------------------------------------------------
135 	// Matching methods.
136 	//-----------------------------------------------------------------------------------------------------------------
137 
138 	public interface B1 {
139 		int foo(int x);
140 		int foo(String x);
141 		int foo();
142 	}
143 	public static class B2 {
144 		public int foo(int x) { return 0; }  // NOSONAR
145 		public int foo(String x) {return 0;}  // NOSONAR
146 		public int foo() {return 0;}
147 	}
148 	public static class B3 extends B2 implements B1 {
149 		@Override public int foo(int x) {return 0;}
150 		@Override public int foo(String x) {return 0;}
151 		@Override public int foo() {return 0;}
152 	}
153 
154 	@Test void findMatchingMethods() throws Exception {
155 		var mi = MethodInfo.of(B3.class.getMethod("foo", int.class));
156 		var l = new ArrayList<MethodInfo>();
157 		mi.forEachMatching(x -> true, l::add);
158 		check("B3.foo(int),B2.foo(int),B1.foo(int)", l);
159 	}
160 
161 	//-----------------------------------------------------------------------------------------------------------------
162 	// Annotations
163 	//-----------------------------------------------------------------------------------------------------------------
164 
165 	@A("C1")
166 	public interface C1 {
167 		@A("a1") void a1();
168 		@A("a2a") void a2();
169 		@A("a3") void a3(CharSequence foo);
170 		void a4();
171 		void a5();
172 	}
173 
174 	@A("C2")
175 	public static class C2 implements C1 {
176 		@Override public void a1() {}  // NOSONAR
177 		@Override @A("a2b") public void a2() {}  // NOSONAR
178 		@Override public void a3(CharSequence s) {}  // NOSONAR
179 		@Override public void a4() {}  // NOSONAR
180 		@Override public void a5() {}  // NOSONAR
181 	}
182 
183 	@A("C3")
184 	public static class C3 extends C2 {
185 		@Override public void a1() {}  // NOSONAR
186 		@Override public void a2() {}  // NOSONAR
187 		@Override public void a3(CharSequence foo) {}  // NOSONAR
188 		@Override @A("a4") public void a4() {}  // NOSONAR
189 		@Override public void a5() {}  // NOSONAR
190 	}
191 
192 	static MethodInfo
193 		c_a1 = ofm(C3.class, "a1"),  // NOSONAR
194 		c_a2 = ofm(C3.class, "a2"),  // NOSONAR
195 		c_a3 = ofm(C3.class, "a3", CharSequence.class),  // NOSONAR
196 		c_a4 = ofm(C3.class, "a4"),  // NOSONAR
197 		c_a5 = ofm(C3.class, "a5");  // NOSONAR
198 
199 	@Test void getAnnotationsParentFirst() {
200 		check("@A(C1),@A(C2),@A(C3),@A(a1)", annotations(c_a1, A.class));
201 		check("@A(C1),@A(C2),@A(C3),@A(a2a),@A(a2b)", annotations(c_a2, A.class));
202 		check("@A(C1),@A(C2),@A(C3),@A(a3)", annotations(c_a3, A.class));
203 		check("@A(C1),@A(C2),@A(C3),@A(a4)", annotations(c_a4, A.class));
204 		check("@A(C1),@A(C2),@A(C3)", annotations(c_a5, A.class));
205 	}
206 
207 	@Test void getAnnotationsParentFirst_notExistent() {
208 		check("", annotations(c_a1, AX.class));
209 		check("", annotations(c_a2, AX.class));
210 		check("", annotations(c_a3, AX.class));
211 		check("", annotations(c_a4, AX.class));
212 		check("", annotations(c_a5, AX.class));
213 	}
214 
215 	private static List<A> annotations(MethodInfo mi, Class<? extends Annotation> a) {
216 		var l = new ArrayList<A>();
217 		mi.forEachAnnotation(a, x -> true, x -> l.add((A)x));
218 		return l;
219 	}
220 
221 	@Test void getAnnotation() {
222 		check("@A(a1)", c_a1.getAnnotation(A.class));
223 		check("@A(a2b)", c_a2.getAnnotation(A.class));
224 		check("@A(a3)", c_a3.getAnnotation(A.class));
225 		check("@A(a4)", c_a4.getAnnotation(A.class));
226 		check(null, c_a5.getAnnotation(A.class));
227 	}
228 
229 	@Test void getAnnotationAny() {
230 		check("@A(a1)", c_a1.getAnyAnnotation(AX.class, A.class));
231 		check("@A(a2b)", c_a2.getAnyAnnotation(AX.class, A.class));
232 		check("@A(a3)", c_a3.getAnyAnnotation(AX.class, A.class));
233 		check("@A(a4)", c_a4.getAnyAnnotation(AX.class, A.class));
234 		check(null, c_a5.getAnyAnnotation(AX.class, A.class));
235 	}
236 
237 	@Test void getAnnotationsMapParentFirst() {
238 		check("@PA(10),@A(C1),@A(a1),@A(C2),@A(C3)", c_a1.getAnnotationList());
239 		check("@PA(10),@A(C1),@A(a2a),@A(C2),@A(a2b),@A(C3)", c_a2.getAnnotationList());
240 		check("@PA(10),@A(C1),@A(a3),@A(C2),@A(C3)", c_a3.getAnnotationList());
241 		check("@PA(10),@A(C1),@A(C2),@A(C3),@A(a4)", c_a4.getAnnotationList());
242 		check("@PA(10),@A(C1),@A(C2),@A(C3)", c_a5.getAnnotationList());
243 	}
244 
245 	@A("C1") @AConfig("C1")
246 	public interface CB1 {
247 		@A("a1") @AConfig("a1") void a1();
248 		@A("a2a") @AConfig("a2a") void a2();
249 		@A("a3") @AConfig("a3") void a3(CharSequence foo);
250 		void a4();
251 		void a5();
252 	}
253 
254 	@A("C2") @AConfig("C2")
255 	public static class CB2 implements CB1 {
256 		@Override public void a1() {}  // NOSONAR
257 		@Override @A("a2b") @AConfig("a2b") public void a2() {}  // NOSONAR
258 		@Override public void a3(CharSequence s) {}  // NOSONAR
259 		@Override public void a4() {}  // NOSONAR
260 		@Override public void a5() {}  // NOSONAR
261 	}
262 
263 	@A("C3") @AConfig("C3")
264 	public static class CB3 extends CB2 {
265 		@Override public void a1() {}  // NOSONAR
266 		@Override public void a2() {}  // NOSONAR
267 		@Override public void a3(CharSequence foo) {}  // NOSONAR
268 		@Override @A("a4") @AConfig("a4") public void a4() {}  // NOSONAR
269 		@Override public void a5() {}  // NOSONAR
270 	}
271 
272 	static MethodInfo
273 		cb_a1 = ofm(CB3.class, "a1"),  // NOSONAR
274 		cb_a2 = ofm(CB3.class, "a2"),  // NOSONAR
275 		cb_a3 = ofm(CB3.class, "a3", CharSequence.class),  // NOSONAR
276 		cb_a4 = ofm(CB3.class, "a4"),  // NOSONAR
277 		cb_a5 = ofm(CB3.class, "a5");  // NOSONAR
278 
279 	@Test void getConfigAnnotationsMapParentFirst() {
280 		check("@AConfig(C1),@AConfig(a1),@AConfig(C2),@AConfig(C3)", cb_a1.getAnnotationList(CONTEXT_APPLY_FILTER));
281 		check("@AConfig(C1),@AConfig(a2a),@AConfig(C2),@AConfig(a2b),@AConfig(C3)", cb_a2.getAnnotationList(CONTEXT_APPLY_FILTER));
282 		check("@AConfig(C1),@AConfig(a3),@AConfig(C2),@AConfig(C3)", cb_a3.getAnnotationList(CONTEXT_APPLY_FILTER));
283 		check("@AConfig(C1),@AConfig(C2),@AConfig(C3),@AConfig(a4)", cb_a4.getAnnotationList(CONTEXT_APPLY_FILTER));
284 		check("@AConfig(C1),@AConfig(C2),@AConfig(C3)", cb_a5.getAnnotationList(CONTEXT_APPLY_FILTER));
285 	}
286 
287 	//-----------------------------------------------------------------------------------------------------------------
288 	// Return type.
289 	//-----------------------------------------------------------------------------------------------------------------
290 
291 	public static class D {
292 		public void a1() {}  // NOSONAR
293 		public Integer a2() {return null;}
294 	}
295 	static MethodInfo
296 		d_a1 = ofm(D.class, "a1"),  // NOSONAR
297 		d_a2 = ofm(D.class, "a2");  // NOSONAR
298 
299 	@Test void getReturnType() {
300 		check("void", d_a1.getReturnType());
301 		check("Integer", d_a2.getReturnType());
302 	}
303 
304 	@Test void hasReturnType() {
305 		assertTrue(d_a1.hasReturnType(void.class));
306 		assertFalse(d_a1.hasReturnType(Integer.class));
307 		assertTrue(d_a2.hasReturnType(Integer.class));
308 		assertFalse(d_a2.hasReturnType(Number.class));
309 	}
310 
311 	@Test void hasReturnTypeParent() {
312 		assertTrue(d_a1.hasReturnTypeParent(void.class));
313 		assertFalse(d_a1.hasReturnTypeParent(Integer.class));
314 		assertTrue(d_a2.hasReturnTypeParent(Integer.class));
315 		assertTrue(d_a2.hasReturnTypeParent(Number.class));
316 	}
317 
318 	//-----------------------------------------------------------------------------------------------------------------
319 	// Other methods
320 	//-----------------------------------------------------------------------------------------------------------------
321 
322 	public static class E {
323 		private String f;
324 		public void a1(CharSequence foo) {
325 			f = foo == null ? null : foo.toString();
326 		}
327 		public void a2(int f1, int f2) {}  // NOSONAR
328 		public void a3() {}  // NOSONAR
329 	}
330 	static MethodInfo
331 		e_a1 = ofm(E.class, "a1", CharSequence.class),  // NOSONAR
332 		e_a2 = ofm(E.class, "a2", int.class, int.class),  // NOSONAR
333 		e_a3 = ofm(E.class, "a3");  // NOSONAR
334 
335 	@Test void invoke() throws Exception {
336 		var e = new E();
337 		e_a1.invoke(e, "foo");
338 		assertEquals("foo", e.f);
339 		e_a1.invoke(e, (CharSequence)null);
340 		assertNull(e.f);
341 	}
342 
343 	@Test void invokeFuzzy() throws Exception {
344 		var e = new E();
345 		e_a1.invokeFuzzy(e, "foo", 123);
346 		assertEquals("foo", e.f);
347 		e_a1.invokeFuzzy(e, 123, "bar");
348 		assertEquals("bar", e.f);
349 	}
350 
351 	@Test void getSignature() {
352 		assertEquals("a1(java.lang.CharSequence)", e_a1.getSignature());
353 		assertEquals("a2(int,int)", e_a2.getSignature());
354 		assertEquals("a3", e_a3.getSignature());
355 	}
356 
357 	@Test void argsOnlyOfType() {
358 		assertTrue(e_a1.argsOnlyOfType(CharSequence.class));
359 		assertTrue(e_a1.argsOnlyOfType(CharSequence.class, Map.class));
360 		assertFalse(e_a1.argsOnlyOfType());
361 	}
362 
363 	public static class F {
364 		public void isA() {}  // NOSONAR
365 		public void is() {}  // NOSONAR
366 		public void getA() {}  // NOSONAR
367 		public void get() {}  // NOSONAR
368 		public void setA() {}  // NOSONAR
369 		public void set() {}  // NOSONAR
370 		public void foo() {}  // NOSONAR
371 	}
372 	static MethodInfo
373 		f_isA = ofm(F.class, "isA"),  // NOSONAR
374 		f_is = ofm(F.class, "is"),  // NOSONAR
375 		f_getA = ofm(F.class, "getA"),  // NOSONAR
376 		f_get = ofm(F.class, "get"),  // NOSONAR
377 		f_setA = ofm(F.class, "setA"),  // NOSONAR
378 		f_set = ofm(F.class, "set"),  // NOSONAR
379 		f_foo = ofm(F.class, "foo");  // NOSONAR
380 
381 	@Test void getPropertyName() {
382 		assertEquals("a", f_isA.getPropertyName());
383 		assertEquals("is", f_is.getPropertyName());
384 		assertEquals("a", f_getA.getPropertyName());
385 		assertEquals("get", f_get.getPropertyName());
386 		assertEquals("a", f_setA.getPropertyName());
387 		assertEquals("set", f_set.getPropertyName());
388 		assertEquals("foo", f_foo.getPropertyName());
389 	}
390 
391 	@Test void isBridge() {
392 		assertFalse(f_foo.isBridge());
393 	}
394 
395 	public static class G {
396 		public void a1() {}  // NOSONAR
397 		public void a1(int a1) {}  // NOSONAR
398 		public void a1(int a1, int a2) {}  // NOSONAR
399 		public void a1(String a1) {}  // NOSONAR
400 		public void a2() {}  // NOSONAR
401 		public void a3() {}  // NOSONAR
402 	}
403 	static MethodInfo
404 		g_a1a = ofm(G.class, "a1"),  // NOSONAR
405 		g_a1b = ofm(G.class, "a1", int.class),  // NOSONAR
406 		g_a1c = ofm(G.class, "a1", int.class, int.class),  // NOSONAR
407 		g_a1d = ofm(G.class, "a1", String.class),  // NOSONAR
408 		g_a2 = ofm(G.class, "a2"),  // NOSONAR
409 		g_a3 = ofm(G.class, "a3");  // NOSONAR
410 
411 	@Test void compareTo() {
412 		var s = new TreeSet<>(Arrays.asList(g_a1a, g_a1b, g_a1c, g_a1d, g_a2, g_a3));
413 		check("[a1(), a1(int), a1(String), a1(int,int), a2(), a3()]", s);
414 	}
415 }