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