1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
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
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 }