View Javadoc
1   /*
2    *    Copyright 2009-2023 the original author or authors.
3    *
4    *    Licensed under the Apache License, Version 2.0 (the "License");
5    *    you may not use this file except in compliance with the License.
6    *    You may obtain a copy of the License at
7    *
8    *       https://www.apache.org/licenses/LICENSE-2.0
9    *
10   *    Unless required by applicable law or agreed to in writing, software
11   *    distributed under the License is distributed on an "AS IS" BASIS,
12   *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *    See the License for the specific language governing permissions and
14   *    limitations under the License.
15   */
16  package org.apache.ibatis.reflection;
17  
18  import static org.junit.jupiter.api.Assertions.assertEquals;
19  import static org.junit.jupiter.api.Assertions.assertTrue;
20  
21  import java.lang.reflect.Field;
22  import java.lang.reflect.GenericArrayType;
23  import java.lang.reflect.Method;
24  import java.lang.reflect.ParameterizedType;
25  import java.lang.reflect.Type;
26  import java.lang.reflect.WildcardType;
27  import java.util.Date;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.concurrent.ExecutorService;
31  import java.util.concurrent.Executors;
32  import java.util.concurrent.Future;
33  
34  import org.apache.ibatis.reflection.typeparam.Calculator;
35  import org.apache.ibatis.reflection.typeparam.Calculator.SubCalculator;
36  import org.apache.ibatis.reflection.typeparam.Level0Mapper;
37  import org.apache.ibatis.reflection.typeparam.Level0Mapper.Level0InnerMapper;
38  import org.apache.ibatis.reflection.typeparam.Level1Mapper;
39  import org.apache.ibatis.reflection.typeparam.Level2Mapper;
40  import org.junit.jupiter.api.Test;
41  
42  class TypeParameterResolverTest {
43    @Test
44    void testReturn_Lv0SimpleClass() throws Exception {
45      Class<?> clazz = Level0Mapper.class;
46      Method method = clazz.getMethod("simpleSelect");
47      Type result = TypeParameterResolver.resolveReturnType(method, clazz);
48      assertEquals(Double.class, result);
49    }
50  
51    @Test
52    void testReturn_SimpleVoid() throws Exception {
53      Class<?> clazz = Level1Mapper.class;
54      Method method = clazz.getMethod("simpleSelectVoid", Integer.class);
55      Type result = TypeParameterResolver.resolveReturnType(method, clazz);
56      assertEquals(void.class, result);
57    }
58  
59    @Test
60    void testReturn_SimplePrimitive() throws Exception {
61      Class<?> clazz = Level1Mapper.class;
62      Method method = clazz.getMethod("simpleSelectPrimitive", int.class);
63      Type result = TypeParameterResolver.resolveReturnType(method, clazz);
64      assertEquals(double.class, result);
65    }
66  
67    @Test
68    void testReturn_SimpleClass() throws Exception {
69      Class<?> clazz = Level1Mapper.class;
70      Method method = clazz.getMethod("simpleSelect");
71      Type result = TypeParameterResolver.resolveReturnType(method, clazz);
72      assertEquals(Double.class, result);
73    }
74  
75    @Test
76    void testReturn_SimpleList() throws Exception {
77      Class<?> clazz = Level1Mapper.class;
78      Method method = clazz.getMethod("simpleSelectList");
79      Type result = TypeParameterResolver.resolveReturnType(method, clazz);
80      assertTrue(result instanceof ParameterizedType);
81      ParameterizedType paramType = (ParameterizedType) result;
82      assertEquals(List.class, paramType.getRawType());
83      assertEquals(1, paramType.getActualTypeArguments().length);
84      assertEquals(Double.class, paramType.getActualTypeArguments()[0]);
85    }
86  
87    @Test
88    void testReturn_SimpleMap() throws Exception {
89      Class<?> clazz = Level1Mapper.class;
90      Method method = clazz.getMethod("simpleSelectMap");
91      Type result = TypeParameterResolver.resolveReturnType(method, clazz);
92      assertTrue(result instanceof ParameterizedType);
93      ParameterizedType paramType = (ParameterizedType) result;
94      assertEquals(Map.class, paramType.getRawType());
95      assertEquals(2, paramType.getActualTypeArguments().length);
96      assertEquals(Integer.class, paramType.getActualTypeArguments()[0]);
97      assertEquals(Double.class, paramType.getActualTypeArguments()[1]);
98    }
99  
100   @Test
101   void testReturn_SimpleWildcard() throws Exception {
102     Class<?> clazz = Level1Mapper.class;
103     Method method = clazz.getMethod("simpleSelectWildcard");
104     Type result = TypeParameterResolver.resolveReturnType(method, clazz);
105     assertTrue(result instanceof ParameterizedType);
106     ParameterizedType paramType = (ParameterizedType) result;
107     assertEquals(List.class, paramType.getRawType());
108     assertEquals(1, paramType.getActualTypeArguments().length);
109     assertTrue(paramType.getActualTypeArguments()[0] instanceof WildcardType);
110     WildcardType wildcard = (WildcardType) paramType.getActualTypeArguments()[0];
111     assertEquals(String.class, wildcard.getUpperBounds()[0]);
112   }
113 
114   @Test
115   void testReturn_SimpleArray() throws Exception {
116     Class<?> clazz = Level1Mapper.class;
117     Method method = clazz.getMethod("simpleSelectArray");
118     Type result = TypeParameterResolver.resolveReturnType(method, clazz);
119     assertTrue(result instanceof Class);
120     Class<?> resultClass = (Class<?>) result;
121     assertTrue(resultClass.isArray());
122     assertEquals(String.class, resultClass.getComponentType());
123   }
124 
125   @Test
126   void testReturn_SimpleArrayOfArray() throws Exception {
127     Class<?> clazz = Level1Mapper.class;
128     Method method = clazz.getMethod("simpleSelectArrayOfArray");
129     Type result = TypeParameterResolver.resolveReturnType(method, clazz);
130     assertTrue(result instanceof Class);
131     Class<?> resultClass = (Class<?>) result;
132     assertTrue(resultClass.isArray());
133     assertTrue(resultClass.getComponentType().isArray());
134     assertEquals(String.class, resultClass.getComponentType().getComponentType());
135   }
136 
137   @Test
138   void testReturn_SimpleTypeVar() throws Exception {
139     Class<?> clazz = Level1Mapper.class;
140     Method method = clazz.getMethod("simpleSelectTypeVar");
141     Type result = TypeParameterResolver.resolveReturnType(method, clazz);
142     assertTrue(result instanceof ParameterizedType);
143     ParameterizedType paramType = (ParameterizedType) result;
144     assertEquals(Calculator.class, paramType.getRawType());
145     assertEquals(1, paramType.getActualTypeArguments().length);
146     assertTrue(paramType.getActualTypeArguments()[0] instanceof WildcardType);
147   }
148 
149   @Test
150   void testReturn_Lv1Class() throws Exception {
151     Class<?> clazz = Level1Mapper.class;
152     Method method = clazz.getMethod("select", Object.class);
153     Type result = TypeParameterResolver.resolveReturnType(method, clazz);
154     assertEquals(String.class, result);
155   }
156 
157   @Test
158   void testReturn_Lv2CustomClass() throws Exception {
159     Class<?> clazz = Level2Mapper.class;
160     Method method = clazz.getMethod("selectCalculator", Calculator.class);
161     Type result = TypeParameterResolver.resolveReturnType(method, clazz);
162     assertTrue(result instanceof ParameterizedType);
163     ParameterizedType paramType = (ParameterizedType) result;
164     assertEquals(Calculator.class, paramType.getRawType());
165     assertEquals(1, paramType.getActualTypeArguments().length);
166     assertEquals(String.class, paramType.getActualTypeArguments()[0]);
167   }
168 
169   @Test
170   void testReturn_Lv2CustomClassList() throws Exception {
171     Class<?> clazz = Level2Mapper.class;
172     Method method = clazz.getMethod("selectCalculatorList");
173     Type result = TypeParameterResolver.resolveReturnType(method, clazz);
174     assertTrue(result instanceof ParameterizedType);
175     ParameterizedType paramTypeOuter = (ParameterizedType) result;
176     assertEquals(List.class, paramTypeOuter.getRawType());
177     assertEquals(1, paramTypeOuter.getActualTypeArguments().length);
178     ParameterizedType paramTypeInner = (ParameterizedType) paramTypeOuter.getActualTypeArguments()[0];
179     assertEquals(Calculator.class, paramTypeInner.getRawType());
180     assertEquals(Date.class, paramTypeInner.getActualTypeArguments()[0]);
181   }
182 
183   @Test
184   void testReturn_Lv0InnerClass() throws Exception {
185     Class<?> clazz = Level0InnerMapper.class;
186     Method method = clazz.getMethod("select", Object.class);
187     Type result = TypeParameterResolver.resolveReturnType(method, clazz);
188     assertEquals(Float.class, result);
189   }
190 
191   @Test
192   void testReturn_Lv2Class() throws Exception {
193     Class<?> clazz = Level2Mapper.class;
194     Method method = clazz.getMethod("select", Object.class);
195     Type result = TypeParameterResolver.resolveReturnType(method, clazz);
196     assertEquals(String.class, result);
197   }
198 
199   @Test
200   void testReturn_Lv1List() throws Exception {
201     Class<?> clazz = Level1Mapper.class;
202     Method method = clazz.getMethod("selectList", Object.class, Object.class);
203     Type result = TypeParameterResolver.resolveReturnType(method, clazz);
204     assertTrue(result instanceof ParameterizedType);
205     ParameterizedType type = (ParameterizedType) result;
206     assertEquals(List.class, type.getRawType());
207     assertEquals(1, type.getActualTypeArguments().length);
208     assertEquals(String.class, type.getActualTypeArguments()[0]);
209   }
210 
211   @Test
212   void testReturn_Lv1Array() throws Exception {
213     Class<?> clazz = Level1Mapper.class;
214     Method method = clazz.getMethod("selectArray", List[].class);
215     Type result = TypeParameterResolver.resolveReturnType(method, clazz);
216     assertTrue(result instanceof Class);
217     Class<?> resultClass = (Class<?>) result;
218     assertTrue(resultClass.isArray());
219     assertEquals(String.class, resultClass.getComponentType());
220   }
221 
222   @Test
223   void testReturn_Lv2ArrayOfArray() throws Exception {
224     Class<?> clazz = Level2Mapper.class;
225     Method method = clazz.getMethod("selectArrayOfArray");
226     Type result = TypeParameterResolver.resolveReturnType(method, clazz);
227     assertTrue(result instanceof Class);
228     Class<?> resultClass = (Class<?>) result;
229     assertTrue(result instanceof Class);
230     assertTrue(resultClass.isArray());
231     assertTrue(resultClass.getComponentType().isArray());
232     assertEquals(String.class, resultClass.getComponentType().getComponentType());
233   }
234 
235   @Test
236   void testReturn_Lv2ArrayOfList() throws Exception {
237     Class<?> clazz = Level2Mapper.class;
238     Method method = clazz.getMethod("selectArrayOfList");
239     Type result = TypeParameterResolver.resolveReturnType(method, clazz);
240     assertTrue(result instanceof GenericArrayType);
241     GenericArrayType genericArrayType = (GenericArrayType) result;
242     assertTrue(genericArrayType.getGenericComponentType() instanceof ParameterizedType);
243     ParameterizedType paramType = (ParameterizedType) genericArrayType.getGenericComponentType();
244     assertEquals(List.class, paramType.getRawType());
245     assertEquals(String.class, paramType.getActualTypeArguments()[0]);
246   }
247 
248   @Test
249   void testReturn_Lv2WildcardList() throws Exception {
250     Class<?> clazz = Level2Mapper.class;
251     Method method = clazz.getMethod("selectWildcardList");
252     Type result = TypeParameterResolver.resolveReturnType(method, clazz);
253     assertTrue(result instanceof ParameterizedType);
254     ParameterizedType type = (ParameterizedType) result;
255     assertEquals(List.class, type.getRawType());
256     assertEquals(1, type.getActualTypeArguments().length);
257     assertTrue(type.getActualTypeArguments()[0] instanceof WildcardType);
258     WildcardType wildcard = (WildcardType) type.getActualTypeArguments()[0];
259     assertEquals(0, wildcard.getLowerBounds().length);
260     assertEquals(1, wildcard.getUpperBounds().length);
261     assertEquals(String.class, wildcard.getUpperBounds()[0]);
262   }
263 
264   @Test
265   void testReturn_LV2Map() throws Exception {
266     Class<?> clazz = Level2Mapper.class;
267     Method method = clazz.getMethod("selectMap");
268     Type result = TypeParameterResolver.resolveReturnType(method, clazz);
269     assertTrue(result instanceof ParameterizedType);
270     ParameterizedType paramType = (ParameterizedType) result;
271     assertEquals(Map.class, paramType.getRawType());
272     assertEquals(2, paramType.getActualTypeArguments().length);
273     assertEquals(String.class, paramType.getActualTypeArguments()[0]);
274     assertEquals(Integer.class, paramType.getActualTypeArguments()[1]);
275   }
276 
277   @Test
278   void testReturn_Subclass() throws Exception {
279     Class<?> clazz = SubCalculator.class;
280     Method method = clazz.getMethod("getId");
281     Type result = TypeParameterResolver.resolveReturnType(method, clazz);
282     assertEquals(String.class, result);
283   }
284 
285   @Test
286   void testParam_Primitive() throws Exception {
287     Class<?> clazz = Level2Mapper.class;
288     Method method = clazz.getMethod("simpleSelectPrimitive", int.class);
289     Type[] result = TypeParameterResolver.resolveParamTypes(method, clazz);
290     assertEquals(1, result.length);
291     assertEquals(int.class, result[0]);
292   }
293 
294   @Test
295   void testParam_Simple() throws Exception {
296     Class<?> clazz = Level1Mapper.class;
297     Method method = clazz.getMethod("simpleSelectVoid", Integer.class);
298     Type[] result = TypeParameterResolver.resolveParamTypes(method, clazz);
299     assertEquals(1, result.length);
300     assertEquals(Integer.class, result[0]);
301   }
302 
303   @Test
304   void testParam_Lv1Single() throws Exception {
305     Class<?> clazz = Level1Mapper.class;
306     Method method = clazz.getMethod("select", Object.class);
307     Type[] result = TypeParameterResolver.resolveParamTypes(method, clazz);
308     assertEquals(1, result.length);
309     assertEquals(String.class, result[0]);
310   }
311 
312   @Test
313   void testParam_Lv2Single() throws Exception {
314     Class<?> clazz = Level2Mapper.class;
315     Method method = clazz.getMethod("select", Object.class);
316     Type[] result = TypeParameterResolver.resolveParamTypes(method, clazz);
317     assertEquals(1, result.length);
318     assertEquals(String.class, result[0]);
319   }
320 
321   @Test
322   void testParam_Lv2Multiple() throws Exception {
323     Class<?> clazz = Level2Mapper.class;
324     Method method = clazz.getMethod("selectList", Object.class, Object.class);
325     Type[] result = TypeParameterResolver.resolveParamTypes(method, clazz);
326     assertEquals(2, result.length);
327     assertEquals(Integer.class, result[0]);
328     assertEquals(String.class, result[1]);
329   }
330 
331   @Test
332   void testParam_Lv2CustomClass() throws Exception {
333     Class<?> clazz = Level2Mapper.class;
334     Method method = clazz.getMethod("selectCalculator", Calculator.class);
335     Type[] result = TypeParameterResolver.resolveParamTypes(method, clazz);
336     assertEquals(1, result.length);
337     assertTrue(result[0] instanceof ParameterizedType);
338     ParameterizedType paramType = (ParameterizedType) result[0];
339     assertEquals(Calculator.class, paramType.getRawType());
340     assertEquals(1, paramType.getActualTypeArguments().length);
341     assertEquals(String.class, paramType.getActualTypeArguments()[0]);
342   }
343 
344   @Test
345   void testParam_Lv1Array() throws Exception {
346     Class<?> clazz = Level1Mapper.class;
347     Method method = clazz.getMethod("selectArray", List[].class);
348     Type[] result = TypeParameterResolver.resolveParamTypes(method, clazz);
349     assertTrue(result[0] instanceof GenericArrayType);
350     GenericArrayType genericArrayType = (GenericArrayType) result[0];
351     assertTrue(genericArrayType.getGenericComponentType() instanceof ParameterizedType);
352     ParameterizedType paramType = (ParameterizedType) genericArrayType.getGenericComponentType();
353     assertEquals(List.class, paramType.getRawType());
354     assertEquals(String.class, paramType.getActualTypeArguments()[0]);
355   }
356 
357   @Test
358   void testParam_Subclass() throws Exception {
359     Class<?> clazz = SubCalculator.class;
360     Method method = clazz.getMethod("setId", Object.class);
361     Type[] result = TypeParameterResolver.resolveParamTypes(method, clazz);
362     assertEquals(String.class, result[0]);
363   }
364 
365   @Test
366   void testReturn_Anonymous() throws Exception {
367     Calculator<?> instance = new Calculator<Integer>();
368     Class<?> clazz = instance.getClass();
369     Method method = clazz.getMethod("getId");
370     Type result = TypeParameterResolver.resolveReturnType(method, clazz);
371     assertEquals(Object.class, result);
372   }
373 
374   @Test
375   void testField_GenericField() throws Exception {
376     Class<?> clazz = SubCalculator.class;
377     Class<?> declaredClass = Calculator.class;
378     Field field = declaredClass.getDeclaredField("fld");
379     Type result = TypeParameterResolver.resolveFieldType(field, clazz);
380     assertEquals(String.class, result);
381   }
382 
383   @Test
384   void testReturnParam_WildcardWithUpperBounds() throws Exception {
385     class Key {
386     }
387     @SuppressWarnings("unused")
388     class KeyBean<S extends Key & Cloneable, T extends Key> {
389       private S key1;
390       private T key2;
391 
392       public S getKey1() {
393         return key1;
394       }
395 
396       public void setKey1(S key1) {
397         this.key1 = key1;
398       }
399 
400       public T getKey2() {
401         return key2;
402       }
403 
404       public void setKey2(T key2) {
405         this.key2 = key2;
406       }
407     }
408     Class<?> clazz = KeyBean.class;
409     Method getter1 = clazz.getMethod("getKey1");
410     assertEquals(Key.class, TypeParameterResolver.resolveReturnType(getter1, clazz));
411     Method setter1 = clazz.getMethod("setKey1", Key.class);
412     assertEquals(Key.class, TypeParameterResolver.resolveParamTypes(setter1, clazz)[0]);
413     Method getter2 = clazz.getMethod("getKey2");
414     assertEquals(Key.class, TypeParameterResolver.resolveReturnType(getter2, clazz));
415     Method setter2 = clazz.getMethod("setKey2", Key.class);
416     assertEquals(Key.class, TypeParameterResolver.resolveParamTypes(setter2, clazz)[0]);
417   }
418 
419   @Test
420   void testDeepHierarchy() throws Exception {
421     @SuppressWarnings("unused")
422     abstract class A<S> {
423       protected S id;
424 
425       public S getId() {
426         return this.id;
427       }
428 
429       public void setId(S id) {
430         this.id = id;
431       }
432     }
433     abstract class B<T> extends A<T> {
434     }
435     abstract class C<U> extends B<U> {
436     }
437     class D extends C<Integer> {
438     }
439     Class<?> clazz = D.class;
440     Method method = clazz.getMethod("getId");
441     assertEquals(Integer.class, TypeParameterResolver.resolveReturnType(method, clazz));
442     Field field = A.class.getDeclaredField("id");
443     assertEquals(Integer.class, TypeParameterResolver.resolveFieldType(field, clazz));
444   }
445 
446   @Test
447   void shouldTypeVariablesBeComparedWithEquals() throws Exception {
448     // #1794
449     ExecutorService executor = Executors.newFixedThreadPool(2);
450     Future<Type> futureA = executor.submit(() -> {
451       Type retType = TypeParameterResolver.resolveReturnType(IfaceA.class.getMethods()[0], IfaceA.class);
452       return ((ParameterizedType) retType).getActualTypeArguments()[0];
453     });
454     Future<Type> futureB = executor.submit(() -> {
455       Type retType = TypeParameterResolver.resolveReturnType(IfaceB.class.getMethods()[0], IfaceB.class);
456       return ((ParameterizedType) retType).getActualTypeArguments()[0];
457     });
458     assertEquals(AA.class, futureA.get());
459     assertEquals(BB.class, futureB.get());
460     executor.shutdown();
461   }
462 
463   // @formatter:off
464   class AA {}
465   class BB {}
466   interface IfaceA extends ParentIface<AA> {}
467   interface IfaceB extends ParentIface<BB> {}
468   interface ParentIface<T> {List<T> m();}
469   // @formatter:on
470 }