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 com.googlecode.catchexception.apis.BDDCatchException.caughtException;
19  import static com.googlecode.catchexception.apis.BDDCatchException.when;
20  import static org.assertj.core.api.BDDAssertions.then;
21  import static org.junit.jupiter.api.Assertions.assertEquals;
22  import static org.junit.jupiter.api.Assertions.assertNotNull;
23  import static org.junit.jupiter.api.Assertions.assertTrue;
24  
25  import java.io.Serializable;
26  import java.util.Arrays;
27  import java.util.List;
28  
29  import org.apache.ibatis.reflection.invoker.Invoker;
30  import org.junit.jupiter.api.Assertions;
31  import org.junit.jupiter.api.Test;
32  
33  class ReflectorTest {
34  
35    @Test
36    void testGetSetterType() {
37      ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
38      Reflector reflector = reflectorFactory.findForClass(Section.class);
39      Assertions.assertEquals(Long.class, reflector.getSetterType("id"));
40    }
41  
42    @Test
43    void testGetGetterType() {
44      ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
45      Reflector reflector = reflectorFactory.findForClass(Section.class);
46      Assertions.assertEquals(Long.class, reflector.getGetterType("id"));
47    }
48  
49    @Test
50    void shouldNotGetClass() {
51      ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
52      Reflector reflector = reflectorFactory.findForClass(Section.class);
53      Assertions.assertFalse(reflector.hasGetter("class"));
54    }
55  
56    interface Entity<T> {
57      T getId();
58  
59      void setId(T id);
60    }
61  
62    static abstract class AbstractEntity implements Entity<Long> {
63  
64      private Long id;
65  
66      @Override
67      public Long getId() {
68        return id;
69      }
70  
71      @Override
72      public void setId(Long id) {
73        this.id = id;
74      }
75    }
76  
77    static class Section extends AbstractEntity implements Entity<Long> {
78    }
79  
80    @Test
81    void shouldResolveSetterParam() {
82      ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
83      Reflector reflector = reflectorFactory.findForClass(Child.class);
84      assertEquals(String.class, reflector.getSetterType("id"));
85    }
86  
87    @Test
88    void shouldResolveParameterizedSetterParam() {
89      ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
90      Reflector reflector = reflectorFactory.findForClass(Child.class);
91      assertEquals(List.class, reflector.getSetterType("list"));
92    }
93  
94    @Test
95    void shouldResolveArraySetterParam() {
96      ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
97      Reflector reflector = reflectorFactory.findForClass(Child.class);
98      Class<?> clazz = reflector.getSetterType("array");
99      assertTrue(clazz.isArray());
100     assertEquals(String.class, clazz.getComponentType());
101   }
102 
103   @Test
104   void shouldResolveGetterType() {
105     ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
106     Reflector reflector = reflectorFactory.findForClass(Child.class);
107     assertEquals(String.class, reflector.getGetterType("id"));
108   }
109 
110   @Test
111   void shouldResolveSetterTypeFromPrivateField() {
112     ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
113     Reflector reflector = reflectorFactory.findForClass(Child.class);
114     assertEquals(String.class, reflector.getSetterType("fld"));
115   }
116 
117   @Test
118   void shouldResolveGetterTypeFromPublicField() {
119     ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
120     Reflector reflector = reflectorFactory.findForClass(Child.class);
121     assertEquals(String.class, reflector.getGetterType("pubFld"));
122   }
123 
124   @Test
125   void shouldResolveParameterizedGetterType() {
126     ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
127     Reflector reflector = reflectorFactory.findForClass(Child.class);
128     assertEquals(List.class, reflector.getGetterType("list"));
129   }
130 
131   @Test
132   void shouldResolveArrayGetterType() {
133     ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
134     Reflector reflector = reflectorFactory.findForClass(Child.class);
135     Class<?> clazz = reflector.getGetterType("array");
136     assertTrue(clazz.isArray());
137     assertEquals(String.class, clazz.getComponentType());
138   }
139 
140   static abstract class Parent<T extends Serializable> {
141     protected T id;
142     protected List<T> list;
143     protected T[] array;
144     private T fld;
145     public T pubFld;
146 
147     public T getId() {
148       return id;
149     }
150 
151     public void setId(T id) {
152       this.id = id;
153     }
154 
155     public List<T> getList() {
156       return list;
157     }
158 
159     public void setList(List<T> list) {
160       this.list = list;
161     }
162 
163     public T[] getArray() {
164       return array;
165     }
166 
167     public void setArray(T[] array) {
168       this.array = array;
169     }
170 
171     public T getFld() {
172       return fld;
173     }
174   }
175 
176   static class Child extends Parent<String> {
177   }
178 
179   @Test
180   void shouldResolveReadonlySetterWithOverload() {
181     class BeanClass implements BeanInterface<String> {
182       @Override
183       public void setId(String id) {
184         // Do nothing
185       }
186     }
187     ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
188     Reflector reflector = reflectorFactory.findForClass(BeanClass.class);
189     assertEquals(String.class, reflector.getSetterType("id"));
190   }
191 
192   interface BeanInterface<T> {
193     void setId(T id);
194   }
195 
196   @Test
197   void shouldSettersWithUnrelatedArgTypesThrowException() throws Exception {
198     @SuppressWarnings("unused")
199     class BeanClass {
200       public void setProp1(String arg) {
201       }
202 
203       public void setProp2(String arg) {
204       }
205 
206       public void setProp2(Integer arg) {
207       }
208 
209       public void setProp2(boolean arg) {
210       }
211     }
212     ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
213     Reflector reflector = reflectorFactory.findForClass(BeanClass.class);
214 
215     List<String> setableProps = Arrays.asList(reflector.getSetablePropertyNames());
216     assertTrue(setableProps.contains("prop1"));
217     assertTrue(setableProps.contains("prop2"));
218     assertEquals("prop1", reflector.findPropertyName("PROP1"));
219     assertEquals("prop2", reflector.findPropertyName("PROP2"));
220 
221     assertEquals(String.class, reflector.getSetterType("prop1"));
222     assertNotNull(reflector.getSetInvoker("prop1"));
223 
224     Class<?> paramType = reflector.getSetterType("prop2");
225     assertTrue(String.class.equals(paramType) || Integer.class.equals(paramType) || boolean.class.equals(paramType));
226 
227     Invoker ambiguousInvoker = reflector.getSetInvoker("prop2");
228     Object[] param = String.class.equals(paramType) ? new String[] { "x" } : new Integer[] { 1 };
229     when(() -> ambiguousInvoker.invoke(new BeanClass(), param));
230     then(caughtException()).isInstanceOf(ReflectionException.class)
231         .hasMessageMatching("Ambiguous setters defined for property 'prop2' in class '"
232             + BeanClass.class.getName().replace("$", "\\$")
233             + "' with types '(java.lang.String|java.lang.Integer|boolean)' and '(java.lang.String|java.lang.Integer|boolean)'\\.");
234   }
235 
236   @Test
237   void shouldTwoGettersForNonBooleanPropertyThrowException() throws Exception {
238     @SuppressWarnings("unused")
239     class BeanClass {
240       public Integer getProp1() {
241         return 1;
242       }
243 
244       public int getProp2() {
245         return 0;
246       }
247 
248       public int isProp2() {
249         return 0;
250       }
251     }
252     ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
253     Reflector reflector = reflectorFactory.findForClass(BeanClass.class);
254 
255     List<String> getableProps = Arrays.asList(reflector.getGetablePropertyNames());
256     assertTrue(getableProps.contains("prop1"));
257     assertTrue(getableProps.contains("prop2"));
258     assertEquals("prop1", reflector.findPropertyName("PROP1"));
259     assertEquals("prop2", reflector.findPropertyName("PROP2"));
260 
261     assertEquals(Integer.class, reflector.getGetterType("prop1"));
262     Invoker getInvoker = reflector.getGetInvoker("prop1");
263     assertEquals(Integer.valueOf(1), getInvoker.invoke(new BeanClass(), null));
264 
265     Class<?> paramType = reflector.getGetterType("prop2");
266     assertEquals(int.class, paramType);
267 
268     Invoker ambiguousInvoker = reflector.getGetInvoker("prop2");
269     when(() -> ambiguousInvoker.invoke(new BeanClass(), new Integer[] { 1 }));
270     then(caughtException()).isInstanceOf(ReflectionException.class)
271         .hasMessageContaining("Illegal overloaded getter method with ambiguous type for property 'prop2' in class '"
272             + BeanClass.class.getName()
273             + "'. This breaks the JavaBeans specification and can cause unpredictable results.");
274   }
275 
276   @Test
277   void shouldTwoGettersWithDifferentTypesThrowException() throws Exception {
278     @SuppressWarnings("unused")
279     class BeanClass {
280       public Integer getProp1() {
281         return 1;
282       }
283 
284       public Integer getProp2() {
285         return 1;
286       }
287 
288       public boolean isProp2() {
289         return false;
290       }
291     }
292     ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
293     Reflector reflector = reflectorFactory.findForClass(BeanClass.class);
294 
295     List<String> getableProps = Arrays.asList(reflector.getGetablePropertyNames());
296     assertTrue(getableProps.contains("prop1"));
297     assertTrue(getableProps.contains("prop2"));
298     assertEquals("prop1", reflector.findPropertyName("PROP1"));
299     assertEquals("prop2", reflector.findPropertyName("PROP2"));
300 
301     assertEquals(Integer.class, reflector.getGetterType("prop1"));
302     Invoker getInvoker = reflector.getGetInvoker("prop1");
303     assertEquals(Integer.valueOf(1), getInvoker.invoke(new BeanClass(), null));
304 
305     Class<?> returnType = reflector.getGetterType("prop2");
306     assertTrue(Integer.class.equals(returnType) || boolean.class.equals(returnType));
307 
308     Invoker ambiguousInvoker = reflector.getGetInvoker("prop2");
309     when(() -> ambiguousInvoker.invoke(new BeanClass(), null));
310     then(caughtException()).isInstanceOf(ReflectionException.class)
311         .hasMessageContaining("Illegal overloaded getter method with ambiguous type for property 'prop2' in class '"
312             + BeanClass.class.getName()
313             + "'. This breaks the JavaBeans specification and can cause unpredictable results.");
314   }
315 
316   @Test
317   void shouldAllowTwoBooleanGetters() throws Exception {
318     @SuppressWarnings("unused")
319     class Bean {
320       // JavaBean Spec allows this (see #906)
321       public boolean isBool() {
322         return true;
323       }
324 
325       public boolean getBool() {
326         return false;
327       }
328 
329       public void setBool(boolean bool) {
330       }
331     }
332     ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
333     Reflector reflector = reflectorFactory.findForClass(Bean.class);
334     assertTrue((Boolean) reflector.getGetInvoker("bool").invoke(new Bean(), new Byte[0]));
335   }
336 
337   @Test
338   void shouldIgnoreBestMatchSetterIfGetterIsAmbiguous() throws Exception {
339     @SuppressWarnings("unused")
340     class Bean {
341       public Integer isBool() {
342         return Integer.valueOf(1);
343       }
344 
345       public Integer getBool() {
346         return Integer.valueOf(2);
347       }
348 
349       public void setBool(boolean bool) {
350       }
351 
352       public void setBool(Integer bool) {
353       }
354     }
355     ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
356     Reflector reflector = reflectorFactory.findForClass(Bean.class);
357     Class<?> paramType = reflector.getSetterType("bool");
358     Object[] param = boolean.class.equals(paramType) ? new Boolean[] { true } : new Integer[] { 1 };
359     Invoker ambiguousInvoker = reflector.getSetInvoker("bool");
360     when(() -> ambiguousInvoker.invoke(new Bean(), param));
361     then(caughtException()).isInstanceOf(ReflectionException.class).hasMessageMatching(
362         "Ambiguous setters defined for property 'bool' in class '" + Bean.class.getName().replace("$", "\\$")
363             + "' with types '(java.lang.Integer|boolean)' and '(java.lang.Integer|boolean)'\\.");
364   }
365 }