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