1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.apache.ibatis.reflection;
17
18 import java.lang.reflect.Constructor;
19 import java.lang.reflect.Field;
20 import java.lang.reflect.Method;
21 import java.lang.reflect.Modifier;
22 import java.lang.reflect.ReflectPermission;
23 import java.util.ArrayList;
24 import java.util.Collection;
25 import java.util.HashMap;
26 import java.util.Iterator;
27 import java.util.List;
28 import java.util.Locale;
29 import java.util.Map;
30
31 import org.apache.ibatis.reflection.invoker.GetFieldInvoker;
32 import org.apache.ibatis.reflection.invoker.Invoker;
33 import org.apache.ibatis.reflection.invoker.MethodInvoker;
34 import org.apache.ibatis.reflection.invoker.SetFieldInvoker;
35 import org.apache.ibatis.reflection.property.PropertyNamer;
36
37
38
39
40
41
42
43
44 public class Reflector {
45
46 private static final String[] EMPTY_STRING_ARRAY = new String[0];
47
48 private Class<?> type;
49 private String[] readablePropertyNames = EMPTY_STRING_ARRAY;
50 private String[] writeablePropertyNames = EMPTY_STRING_ARRAY;
51 private Map<String, Invoker> setMethods = new HashMap<String, Invoker>();
52 private Map<String, Invoker> getMethods = new HashMap<String, Invoker>();
53 private Map<String, Class<?>> setTypes = new HashMap<String, Class<?>>();
54 private Map<String, Class<?>> getTypes = new HashMap<String, Class<?>>();
55 private Constructor<?> defaultConstructor;
56
57 private Map<String, String> caseInsensitivePropertyMap = new HashMap<String, String>();
58
59 public Reflector(Class<?> clazz) {
60 type = clazz;
61 addDefaultConstructor(clazz);
62 addGetMethods(clazz);
63 addSetMethods(clazz);
64 addFields(clazz);
65 readablePropertyNames = getMethods.keySet().toArray(new String[getMethods.keySet().size()]);
66 writeablePropertyNames = setMethods.keySet().toArray(new String[setMethods.keySet().size()]);
67 for (String propName : readablePropertyNames) {
68 caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
69 }
70 for (String propName : writeablePropertyNames) {
71 caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
72 }
73 }
74
75 private void addDefaultConstructor(Class<?> clazz) {
76 Constructor<?>[] consts = clazz.getDeclaredConstructors();
77 for (Constructor<?> constructor : consts) {
78 if (constructor.getParameterTypes().length == 0) {
79 if (canAccessPrivateMethods()) {
80 try {
81 constructor.setAccessible(true);
82 } catch (Exception e) {
83
84 }
85 }
86 if (constructor.isAccessible()) {
87 this.defaultConstructor = constructor;
88 }
89 }
90 }
91 }
92
93 private void addGetMethods(Class<?> cls) {
94 Map<String, List<Method>> conflictingGetters = new HashMap<String, List<Method>>();
95 Method[] methods = getClassMethods(cls);
96 for (Method method : methods) {
97 String name = method.getName();
98 if (name.startsWith("get") && name.length() > 3) {
99 if (method.getParameterTypes().length == 0) {
100 name = PropertyNamer.methodToProperty(name);
101 addMethodConflict(conflictingGetters, name, method);
102 }
103 } else if (name.startsWith("is") && name.length() > 2) {
104 if (method.getParameterTypes().length == 0) {
105 name = PropertyNamer.methodToProperty(name);
106 addMethodConflict(conflictingGetters, name, method);
107 }
108 }
109 }
110 resolveGetterConflicts(conflictingGetters);
111 }
112
113 private void resolveGetterConflicts(Map<String, List<Method>> conflictingGetters) {
114 for (String propName : conflictingGetters.keySet()) {
115 List<Method> getters = conflictingGetters.get(propName);
116 Iterator<Method> iterator = getters.iterator();
117 Method firstMethod = iterator.next();
118 if (getters.size() == 1) {
119 addGetMethod(propName, firstMethod);
120 } else {
121 Method getter = firstMethod;
122 Class<?> getterType = firstMethod.getReturnType();
123 while (iterator.hasNext()) {
124 Method method = iterator.next();
125 Class<?> methodType = method.getReturnType();
126 if (methodType.equals(getterType)) {
127 throw new ReflectionException("Illegal overloaded getter method with ambiguous type for property "
128 + propName + " in class " + firstMethod.getDeclaringClass()
129 + ". This breaks the JavaBeans " + "specification and can cause unpredicatble results.");
130 } else if (methodType.isAssignableFrom(getterType)) {
131
132 } else if (getterType.isAssignableFrom(methodType)) {
133 getter = method;
134 getterType = methodType;
135 } else {
136 throw new ReflectionException("Illegal overloaded getter method with ambiguous type for property "
137 + propName + " in class " + firstMethod.getDeclaringClass()
138 + ". This breaks the JavaBeans " + "specification and can cause unpredicatble results.");
139 }
140 }
141 addGetMethod(propName, getter);
142 }
143 }
144 }
145
146 private void addGetMethod(String name, Method method) {
147 if (isValidPropertyName(name)) {
148 getMethods.put(name, new MethodInvoker(method));
149 getTypes.put(name, method.getReturnType());
150 }
151 }
152
153 private void addSetMethods(Class<?> cls) {
154 Map<String, List<Method>> conflictingSetters = new HashMap<String, List<Method>>();
155 Method[] methods = getClassMethods(cls);
156 for (Method method : methods) {
157 String name = method.getName();
158 if (name.startsWith("set") && name.length() > 3) {
159 if (method.getParameterTypes().length == 1) {
160 name = PropertyNamer.methodToProperty(name);
161 addMethodConflict(conflictingSetters, name, method);
162 }
163 }
164 }
165 resolveSetterConflicts(conflictingSetters);
166 }
167
168 private void addMethodConflict(Map<String, List<Method>> conflictingMethods, String name, Method method) {
169 List<Method> list = conflictingMethods.get(name);
170 if (list == null) {
171 list = new ArrayList<Method>();
172 conflictingMethods.put(name, list);
173 }
174 list.add(method);
175 }
176
177 private void resolveSetterConflicts(Map<String, List<Method>> conflictingSetters) {
178 for (String propName : conflictingSetters.keySet()) {
179 List<Method> setters = conflictingSetters.get(propName);
180 Method firstMethod = setters.get(0);
181 if (setters.size() == 1) {
182 addSetMethod(propName, firstMethod);
183 } else {
184 Class<?> expectedType = getTypes.get(propName);
185 if (expectedType == null) {
186 throw new ReflectionException("Illegal overloaded setter method with ambiguous type for property "
187 + propName + " in class " + firstMethod.getDeclaringClass() + ". This breaks the JavaBeans " +
188 "specification and can cause unpredicatble results.");
189 } else {
190 Iterator<Method> methods = setters.iterator();
191 Method setter = null;
192 while (methods.hasNext()) {
193 Method method = methods.next();
194 if (method.getParameterTypes().length == 1
195 && expectedType.equals(method.getParameterTypes()[0])) {
196 setter = method;
197 break;
198 }
199 }
200 if (setter == null) {
201 throw new ReflectionException("Illegal overloaded setter method with ambiguous type for property "
202 + propName + " in class " + firstMethod.getDeclaringClass() + ". This breaks the JavaBeans " +
203 "specification and can cause unpredicatble results.");
204 }
205 addSetMethod(propName, setter);
206 }
207 }
208 }
209 }
210
211 private void addSetMethod(String name, Method method) {
212 if (isValidPropertyName(name)) {
213 setMethods.put(name, new MethodInvoker(method));
214 setTypes.put(name, method.getParameterTypes()[0]);
215 }
216 }
217
218 private void addFields(Class<?> clazz) {
219 Field[] fields = clazz.getDeclaredFields();
220 for (Field field : fields) {
221 if (canAccessPrivateMethods()) {
222 try {
223 field.setAccessible(true);
224 } catch (Exception e) {
225
226 }
227 }
228 if (field.isAccessible()) {
229 if (!setMethods.containsKey(field.getName())) {
230
231
232
233 int modifiers = field.getModifiers();
234 if (!(Modifier.isFinal(modifiers) && Modifier.isStatic(modifiers))) {
235 addSetField(field);
236 }
237 }
238 if (!getMethods.containsKey(field.getName())) {
239 addGetField(field);
240 }
241 }
242 }
243 if (clazz.getSuperclass() != null) {
244 addFields(clazz.getSuperclass());
245 }
246 }
247
248 private void addSetField(Field field) {
249 if (isValidPropertyName(field.getName())) {
250 setMethods.put(field.getName(), new SetFieldInvoker(field));
251 setTypes.put(field.getName(), field.getType());
252 }
253 }
254
255 private void addGetField(Field field) {
256 if (isValidPropertyName(field.getName())) {
257 getMethods.put(field.getName(), new GetFieldInvoker(field));
258 getTypes.put(field.getName(), field.getType());
259 }
260 }
261
262 private boolean isValidPropertyName(String name) {
263 return !(name.startsWith("$") || "serialVersionUID".equals(name) || "class".equals(name));
264 }
265
266
267
268
269
270
271
272
273
274
275 private Method[] getClassMethods(Class<?> cls) {
276 Map<String, Method> uniqueMethods = new HashMap<String, Method>();
277 Class<?> currentClass = cls;
278 while (currentClass != null) {
279 addUniqueMethods(uniqueMethods, currentClass.getDeclaredMethods());
280
281
282
283 Class<?>[] interfaces = currentClass.getInterfaces();
284 for (Class<?> anInterface : interfaces) {
285 addUniqueMethods(uniqueMethods, anInterface.getMethods());
286 }
287
288 currentClass = currentClass.getSuperclass();
289 }
290
291 Collection<Method> methods = uniqueMethods.values();
292
293 return methods.toArray(new Method[methods.size()]);
294 }
295
296 private void addUniqueMethods(Map<String, Method> uniqueMethods, Method[] methods) {
297 for (Method currentMethod : methods) {
298 if (!currentMethod.isBridge()) {
299 String signature = getSignature(currentMethod);
300
301
302
303 if (!uniqueMethods.containsKey(signature)) {
304 if (canAccessPrivateMethods()) {
305 try {
306 currentMethod.setAccessible(true);
307 } catch (Exception e) {
308
309 }
310 }
311
312 uniqueMethods.put(signature, currentMethod);
313 }
314 }
315 }
316 }
317
318 private String getSignature(Method method) {
319 StringBuilder sb = new StringBuilder();
320 Class<?> returnType = method.getReturnType();
321 if (returnType != null) {
322 sb.append(returnType.getName()).append('#');
323 }
324 sb.append(method.getName());
325 Class<?>[] parameters = method.getParameterTypes();
326 for (int i = 0; i < parameters.length; i++) {
327 if (i == 0) {
328 sb.append(':');
329 } else {
330 sb.append(',');
331 }
332 sb.append(parameters[i].getName());
333 }
334 return sb.toString();
335 }
336
337 private static boolean canAccessPrivateMethods() {
338 try {
339 SecurityManager securityManager = System.getSecurityManager();
340 if (null != securityManager) {
341 securityManager.checkPermission(new ReflectPermission("suppressAccessChecks"));
342 }
343 } catch (SecurityException e) {
344 return false;
345 }
346 return true;
347 }
348
349
350
351
352
353
354 public Class<?> getType() {
355 return type;
356 }
357
358 public Constructor<?> getDefaultConstructor() {
359 if (defaultConstructor != null) {
360 return defaultConstructor;
361 } else {
362 throw new ReflectionException("There is no default constructor for " + type);
363 }
364 }
365
366 public boolean hasDefaultConstructor() {
367 return defaultConstructor != null;
368 }
369
370 public Invoker getSetInvoker(String propertyName) {
371 Invoker method = setMethods.get(propertyName);
372 if (method == null) {
373 throw new ReflectionException("There is no setter for property named '" + propertyName + "' in '" + type + "'");
374 }
375 return method;
376 }
377
378 public Invoker getGetInvoker(String propertyName) {
379 Invoker method = getMethods.get(propertyName);
380 if (method == null) {
381 throw new ReflectionException("There is no getter for property named '" + propertyName + "' in '" + type + "'");
382 }
383 return method;
384 }
385
386
387
388
389
390
391
392 public Class<?> getSetterType(String propertyName) {
393 Class<?> clazz = setTypes.get(propertyName);
394 if (clazz == null) {
395 throw new ReflectionException("There is no setter for property named '" + propertyName + "' in '" + type + "'");
396 }
397 return clazz;
398 }
399
400
401
402
403
404
405
406 public Class<?> getGetterType(String propertyName) {
407 Class<?> clazz = getTypes.get(propertyName);
408 if (clazz == null) {
409 throw new ReflectionException("There is no getter for property named '" + propertyName + "' in '" + type + "'");
410 }
411 return clazz;
412 }
413
414
415
416
417
418
419 public String[] getGetablePropertyNames() {
420 return readablePropertyNames;
421 }
422
423
424
425
426
427
428 public String[] getSetablePropertyNames() {
429 return writeablePropertyNames;
430 }
431
432
433
434
435
436
437
438 public boolean hasSetter(String propertyName) {
439 return setMethods.keySet().contains(propertyName);
440 }
441
442
443
444
445
446
447
448 public boolean hasGetter(String propertyName) {
449 return getMethods.keySet().contains(propertyName);
450 }
451
452 public String findPropertyName(String name) {
453 return caseInsensitivePropertyMap.get(name.toUpperCase(Locale.ENGLISH));
454 }
455 }