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