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.*;
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
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
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 }