View Javadoc
1   /*
2    *    Copyright 2009-2024 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 returnLv0SimpleClass() 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 returnSimpleVoid() 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 returnSimplePrimitive() 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 returnSimpleClass() 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 returnSimpleList() 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 returnSimpleMap() 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 returnSimpleWildcard() 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 returnSimpleArray() 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 returnSimpleArrayOfArray() 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 returnSimpleTypeVar() 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 returnLv1Class() 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 returnLv2CustomClass() 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 returnLv2CustomClassList() 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 returnLv0InnerClass() 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 returnLv2Class() 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 returnLv1List() 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 returnLv1Array() 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 returnLv2ArrayOfArray() 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 returnLv2ArrayOfList() 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 returnLv2WildcardList() 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 returnLV1Map() throws Exception {
266     Class<?> clazz = Level1Mapper.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(Object.class, paramType.getActualTypeArguments()[1]);
275   }
276 
277   @Test
278   void returnLV2Map() throws Exception {
279     Class<?> clazz = Level2Mapper.class;
280     Method method = clazz.getMethod("selectMap");
281     Type result = TypeParameterResolver.resolveReturnType(method, clazz);
282     assertTrue(result instanceof ParameterizedType);
283     ParameterizedType paramType = (ParameterizedType) result;
284     assertEquals(Map.class, paramType.getRawType());
285     assertEquals(2, paramType.getActualTypeArguments().length);
286     assertEquals(String.class, paramType.getActualTypeArguments()[0]);
287     assertEquals(Integer.class, paramType.getActualTypeArguments()[1]);
288   }
289 
290   @Test
291   void returnSubclass() throws Exception {
292     Class<?> clazz = SubCalculator.class;
293     Method method = clazz.getMethod("getId");
294     Type result = TypeParameterResolver.resolveReturnType(method, clazz);
295     assertEquals(String.class, result);
296   }
297 
298   @Test
299   void paramPrimitive() throws Exception {
300     Class<?> clazz = Level2Mapper.class;
301     Method method = clazz.getMethod("simpleSelectPrimitive", int.class);
302     Type[] result = TypeParameterResolver.resolveParamTypes(method, clazz);
303     assertEquals(1, result.length);
304     assertEquals(int.class, result[0]);
305   }
306 
307   @Test
308   void paramSimple() throws Exception {
309     Class<?> clazz = Level1Mapper.class;
310     Method method = clazz.getMethod("simpleSelectVoid", Integer.class);
311     Type[] result = TypeParameterResolver.resolveParamTypes(method, clazz);
312     assertEquals(1, result.length);
313     assertEquals(Integer.class, result[0]);
314   }
315 
316   @Test
317   void paramLv1Single() throws Exception {
318     Class<?> clazz = Level1Mapper.class;
319     Method method = clazz.getMethod("select", Object.class);
320     Type[] result = TypeParameterResolver.resolveParamTypes(method, clazz);
321     assertEquals(1, result.length);
322     assertEquals(String.class, result[0]);
323   }
324 
325   @Test
326   void paramLv2Single() throws Exception {
327     Class<?> clazz = Level2Mapper.class;
328     Method method = clazz.getMethod("select", Object.class);
329     Type[] result = TypeParameterResolver.resolveParamTypes(method, clazz);
330     assertEquals(1, result.length);
331     assertEquals(String.class, result[0]);
332   }
333 
334   @Test
335   void paramLv2Multiple() throws Exception {
336     Class<?> clazz = Level2Mapper.class;
337     Method method = clazz.getMethod("selectList", Object.class, Object.class);
338     Type[] result = TypeParameterResolver.resolveParamTypes(method, clazz);
339     assertEquals(2, result.length);
340     assertEquals(Integer.class, result[0]);
341     assertEquals(String.class, result[1]);
342   }
343 
344   @Test
345   void paramLv2CustomClass() throws Exception {
346     Class<?> clazz = Level2Mapper.class;
347     Method method = clazz.getMethod("selectCalculator", Calculator.class);
348     Type[] result = TypeParameterResolver.resolveParamTypes(method, clazz);
349     assertEquals(1, result.length);
350     assertTrue(result[0] instanceof ParameterizedType);
351     ParameterizedType paramType = (ParameterizedType) result[0];
352     assertEquals(Calculator.class, paramType.getRawType());
353     assertEquals(1, paramType.getActualTypeArguments().length);
354     assertEquals(String.class, paramType.getActualTypeArguments()[0]);
355   }
356 
357   @Test
358   void paramLv1Array() throws Exception {
359     Class<?> clazz = Level1Mapper.class;
360     Method method = clazz.getMethod("selectArray", List[].class);
361     Type[] result = TypeParameterResolver.resolveParamTypes(method, clazz);
362     assertTrue(result[0] instanceof GenericArrayType);
363     GenericArrayType genericArrayType = (GenericArrayType) result[0];
364     assertTrue(genericArrayType.getGenericComponentType() instanceof ParameterizedType);
365     ParameterizedType paramType = (ParameterizedType) genericArrayType.getGenericComponentType();
366     assertEquals(List.class, paramType.getRawType());
367     assertEquals(String.class, paramType.getActualTypeArguments()[0]);
368   }
369 
370   @Test
371   void paramSubclass() throws Exception {
372     Class<?> clazz = SubCalculator.class;
373     Method method = clazz.getMethod("setId", Object.class);
374     Type[] result = TypeParameterResolver.resolveParamTypes(method, clazz);
375     assertEquals(String.class, result[0]);
376   }
377 
378   @Test
379   void returnAnonymous() throws Exception {
380     Calculator<?> instance = new Calculator<Integer>();
381     Class<?> clazz = instance.getClass();
382     Method method = clazz.getMethod("getId");
383     Type result = TypeParameterResolver.resolveReturnType(method, clazz);
384     assertEquals(Object.class, result);
385   }
386 
387   @Test
388   void fieldGenericField() throws Exception {
389     Class<?> clazz = SubCalculator.class;
390     Class<?> declaredClass = Calculator.class;
391     Field field = declaredClass.getDeclaredField("fld");
392     Type result = TypeParameterResolver.resolveFieldType(field, clazz);
393     assertEquals(String.class, result);
394   }
395 
396   @Test
397   void returnParamWildcardWithUpperBounds() throws Exception {
398     class Key {
399     }
400     @SuppressWarnings("unused")
401     class KeyBean<S extends Key & Cloneable, T extends Key> {
402       private S key1;
403       private T key2;
404 
405       public S getKey1() {
406         return key1;
407       }
408 
409       public void setKey1(S key1) {
410         this.key1 = key1;
411       }
412 
413       public T getKey2() {
414         return key2;
415       }
416 
417       public void setKey2(T key2) {
418         this.key2 = key2;
419       }
420     }
421     Class<?> clazz = KeyBean.class;
422     Method getter1 = clazz.getMethod("getKey1");
423     assertEquals(Key.class, TypeParameterResolver.resolveReturnType(getter1, clazz));
424     Method setter1 = clazz.getMethod("setKey1", Key.class);
425     assertEquals(Key.class, TypeParameterResolver.resolveParamTypes(setter1, clazz)[0]);
426     Method getter2 = clazz.getMethod("getKey2");
427     assertEquals(Key.class, TypeParameterResolver.resolveReturnType(getter2, clazz));
428     Method setter2 = clazz.getMethod("setKey2", Key.class);
429     assertEquals(Key.class, TypeParameterResolver.resolveParamTypes(setter2, clazz)[0]);
430   }
431 
432   @Test
433   void deepHierarchy() throws Exception {
434     @SuppressWarnings("unused")
435     abstract class A<S> {
436       protected S id;
437 
438       public S getId() {
439         return this.id;
440       }
441 
442       public void setId(S id) {
443         this.id = id;
444       }
445     }
446     abstract class B<T> extends A<T> {
447     }
448     abstract class C<U> extends B<U> {
449     }
450     class D extends C<Integer> {
451     }
452     Class<?> clazz = D.class;
453     Method method = clazz.getMethod("getId");
454     assertEquals(Integer.class, TypeParameterResolver.resolveReturnType(method, clazz));
455     Field field = A.class.getDeclaredField("id");
456     assertEquals(Integer.class, TypeParameterResolver.resolveFieldType(field, clazz));
457   }
458 
459   @Test
460   void shouldTypeVariablesBeComparedWithEquals() throws Exception {
461     // #1794
462     ExecutorService executor = Executors.newFixedThreadPool(2);
463     Future<Type> futureA = executor.submit(() -> {
464       Type retType = TypeParameterResolver.resolveReturnType(IfaceA.class.getMethods()[0], IfaceA.class);
465       return ((ParameterizedType) retType).getActualTypeArguments()[0];
466     });
467     Future<Type> futureB = executor.submit(() -> {
468       Type retType = TypeParameterResolver.resolveReturnType(IfaceB.class.getMethods()[0], IfaceB.class);
469       return ((ParameterizedType) retType).getActualTypeArguments()[0];
470     });
471     assertEquals(AA.class, futureA.get());
472     assertEquals(BB.class, futureB.get());
473     executor.shutdown();
474   }
475 
476   class AA {
477     // Do nothing
478   }
479 
480   class BB {
481     // Do nothing
482   }
483 
484   interface IfaceA extends ParentIface<AA> {
485     // Do Nothing
486   }
487 
488   interface IfaceB extends ParentIface<BB> {
489     // Do Nothing
490   }
491 
492   interface ParentIface<T> {
493     List<T> m();
494   }
495 
496 }