1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package com.ibatis.common.beans;
17
18 import java.lang.reflect.Constructor;
19 import java.lang.reflect.Field;
20 import java.lang.reflect.InvocationTargetException;
21 import java.lang.reflect.Method;
22 import java.lang.reflect.ReflectPermission;
23 import java.lang.reflect.UndeclaredThrowableException;
24 import java.math.BigDecimal;
25 import java.math.BigInteger;
26 import java.util.ArrayList;
27 import java.util.Collection;
28 import java.util.Date;
29 import java.util.Enumeration;
30 import java.util.HashMap;
31 import java.util.HashSet;
32 import java.util.Hashtable;
33 import java.util.Iterator;
34 import java.util.LinkedList;
35 import java.util.List;
36 import java.util.Locale;
37 import java.util.Map;
38 import java.util.Set;
39 import java.util.TreeMap;
40 import java.util.TreeSet;
41 import java.util.Vector;
42 import java.util.concurrent.ConcurrentHashMap;
43
44
45
46
47
48 public class ClassInfo {
49
50
51 private static boolean cacheEnabled = true;
52
53
54 private static final String[] EMPTY_STRING_ARRAY = {};
55
56
57 private static final Set SIMPLE_TYPE_SET = new HashSet<>();
58
59
60 private static final Map<Class, ClassInfo> CLASS_INFO_MAP = new ConcurrentHashMap<>();
61
62
63 private String className;
64
65
66 private String[] readablePropertyNames = EMPTY_STRING_ARRAY;
67
68
69 private String[] writeablePropertyNames = EMPTY_STRING_ARRAY;
70
71
72 private HashMap setMethods = new HashMap<>();
73
74
75 private HashMap getMethods = new HashMap<>();
76
77
78 private HashMap setTypes = new HashMap<>();
79
80
81 private HashMap getTypes = new HashMap<>();
82
83
84 private Constructor defaultConstructor;
85
86 static {
87 SIMPLE_TYPE_SET.add(String.class);
88 SIMPLE_TYPE_SET.add(Byte.class);
89 SIMPLE_TYPE_SET.add(Short.class);
90 SIMPLE_TYPE_SET.add(Character.class);
91 SIMPLE_TYPE_SET.add(Integer.class);
92 SIMPLE_TYPE_SET.add(Long.class);
93 SIMPLE_TYPE_SET.add(Float.class);
94 SIMPLE_TYPE_SET.add(Double.class);
95 SIMPLE_TYPE_SET.add(Boolean.class);
96 SIMPLE_TYPE_SET.add(Date.class);
97 SIMPLE_TYPE_SET.add(Class.class);
98 SIMPLE_TYPE_SET.add(BigInteger.class);
99 SIMPLE_TYPE_SET.add(BigDecimal.class);
100
101 SIMPLE_TYPE_SET.add(Collection.class);
102 SIMPLE_TYPE_SET.add(Set.class);
103 SIMPLE_TYPE_SET.add(Map.class);
104 SIMPLE_TYPE_SET.add(List.class);
105 SIMPLE_TYPE_SET.add(HashMap.class);
106 SIMPLE_TYPE_SET.add(TreeMap.class);
107 SIMPLE_TYPE_SET.add(ArrayList.class);
108 SIMPLE_TYPE_SET.add(LinkedList.class);
109 SIMPLE_TYPE_SET.add(HashSet.class);
110 SIMPLE_TYPE_SET.add(TreeSet.class);
111 SIMPLE_TYPE_SET.add(Vector.class);
112 SIMPLE_TYPE_SET.add(Hashtable.class);
113 SIMPLE_TYPE_SET.add(Enumeration.class);
114 }
115
116
117
118
119
120
121
122 private ClassInfo(Class clazz) {
123 className = clazz.getName();
124 addDefaultConstructor(clazz);
125 addGetMethods(clazz);
126 addSetMethods(clazz);
127 addFields(clazz);
128 readablePropertyNames = (String[]) getMethods.keySet().toArray(new String[getMethods.size()]);
129 writeablePropertyNames = (String[]) setMethods.keySet().toArray(new String[setMethods.size()]);
130 }
131
132
133
134
135
136
137
138 private void addDefaultConstructor(Class clazz) {
139 Constructor[] consts = clazz.getDeclaredConstructors();
140 for (Constructor constructor : consts) {
141 if (constructor.getParameterTypes().length == 0) {
142 if (canAccessPrivateMethods()) {
143 try {
144 constructor.setAccessible(true);
145 } catch (Exception e) {
146
147 }
148 }
149 if (constructor.isAccessible()) {
150 this.defaultConstructor = constructor;
151 }
152 }
153 }
154 }
155
156
157
158
159
160
161
162 private void addGetMethods(Class cls) {
163 Method[] methods = getClassMethods(cls);
164 for (Method method : methods) {
165 String name = method.getName();
166 if (name.startsWith("get") && name.length() > 3 || name.startsWith("is") && name.length() > 2) {
167 if (method.getParameterTypes().length == 0) {
168 name = dropCase(name);
169 addGetMethod(name, method);
170 }
171 }
172 }
173 }
174
175
176
177
178
179
180
181
182
183 private void addGetMethod(String name, Method method) {
184 getMethods.put(name, new MethodInvoker(method));
185 getTypes.put(name, method.getReturnType());
186 }
187
188
189
190
191
192
193
194 private void addSetMethods(Class cls) {
195 Map conflictingSetters = new HashMap<>();
196 Method[] methods = getClassMethods(cls);
197 for (Method method : methods) {
198 String name = method.getName();
199 if (name.startsWith("set") && name.length() > 3 && method.getParameterTypes().length == 1) {
200 name = dropCase(name);
201
202 addSetterConflict(conflictingSetters, name, method);
203
204
205 }
206 }
207 resolveSetterConflicts(conflictingSetters);
208 }
209
210
211
212
213
214
215
216
217
218
219
220 private void addSetterConflict(Map conflictingSetters, String name, Method method) {
221 List list = (List) conflictingSetters.get(name);
222 if (list == null) {
223 list = new ArrayList<>();
224 conflictingSetters.put(name, list);
225 }
226 list.add(method);
227 }
228
229
230
231
232
233
234
235 private void resolveSetterConflicts(Map conflictingSetters) {
236 for (Iterator propNames = conflictingSetters.keySet().iterator(); propNames.hasNext();) {
237 String propName = (String) propNames.next();
238 List setters = (List) conflictingSetters.get(propName);
239 Method firstMethod = (Method) setters.get(0);
240 if (setters.size() == 1) {
241 addSetMethod(propName, firstMethod);
242 } else {
243 Class expectedType = (Class) getTypes.get(propName);
244 if (expectedType == null) {
245 throw new RuntimeException("Illegal overloaded setter method with ambiguous type for property " + propName
246 + " in class " + firstMethod.getDeclaringClass() + ". This breaks the JavaBeans "
247 + "specification and can cause unpredicatble results.");
248 }
249 Iterator methods = setters.iterator();
250 Method setter = null;
251 while (methods.hasNext()) {
252 Method method = (Method) methods.next();
253 if (method.getParameterTypes().length == 1 && expectedType.equals(method.getParameterTypes()[0])) {
254 setter = method;
255 break;
256 }
257 }
258 if (setter == null) {
259 throw new RuntimeException("Illegal overloaded setter method with ambiguous type for property " + propName
260 + " in class " + firstMethod.getDeclaringClass() + ". This breaks the JavaBeans "
261 + "specification and can cause unpredicatble results.");
262 }
263 addSetMethod(propName, setter);
264 }
265 }
266 }
267
268
269
270
271
272
273
274
275
276 private void addSetMethod(String name, Method method) {
277 setMethods.put(name, new MethodInvoker(method));
278 setTypes.put(name, method.getParameterTypes()[0]);
279 }
280
281
282
283
284
285
286
287 private void addFields(Class clazz) {
288 Field[] fields = clazz.getDeclaredFields();
289 for (Field field : fields) {
290 if (canAccessPrivateMethods()) {
291 try {
292 field.setAccessible(true);
293 } catch (Exception e) {
294
295 }
296 }
297 if (field.isAccessible()) {
298 if (!setMethods.containsKey(field.getName())) {
299 addSetField(field);
300 }
301 if (!getMethods.containsKey(field.getName())) {
302 addGetField(field);
303 }
304 }
305 }
306 if (clazz.getSuperclass() != null) {
307 addFields(clazz.getSuperclass());
308 }
309 }
310
311
312
313
314
315
316
317 private void addSetField(Field field) {
318 setMethods.put(field.getName(), new SetFieldInvoker(field));
319 setTypes.put(field.getName(), field.getType());
320 }
321
322
323
324
325
326
327
328 private void addGetField(Field field) {
329 getMethods.put(field.getName(), new GetFieldInvoker(field));
330 getTypes.put(field.getName(), field.getType());
331 }
332
333
334
335
336
337
338
339
340
341
342 private Method[] getClassMethods(Class cls) {
343 HashMap uniqueMethods = new HashMap<>();
344 Class currentClass = cls;
345 while (currentClass != null) {
346 addUniqueMethods(uniqueMethods, currentClass.getDeclaredMethods());
347
348
349
350 Class[] interfaces = currentClass.getInterfaces();
351 for (Class element : interfaces) {
352 addUniqueMethods(uniqueMethods, element.getMethods());
353 }
354
355 currentClass = currentClass.getSuperclass();
356 }
357
358 Collection methods = uniqueMethods.values();
359
360 return (Method[]) methods.toArray(new Method[methods.size()]);
361 }
362
363
364
365
366
367
368
369
370
371 private void addUniqueMethods(HashMap uniqueMethods, Method[] methods) {
372 for (Method currentMethod : methods) {
373 if (!currentMethod.isBridge()) {
374 String signature = getSignature(currentMethod);
375
376
377
378 if (!uniqueMethods.containsKey(signature)) {
379 if (canAccessPrivateMethods()) {
380 try {
381 currentMethod.setAccessible(true);
382 } catch (Exception e) {
383
384 }
385 }
386
387 uniqueMethods.put(signature, currentMethod);
388 }
389 }
390 }
391 }
392
393
394
395
396
397
398
399
400
401 private String getSignature(Method method) {
402 StringBuilder sb = new StringBuilder();
403 sb.append(method.getName());
404 Class[] parameters = method.getParameterTypes();
405
406 for (int i = 0; i < parameters.length; i++) {
407 if (i == 0) {
408 sb.append(':');
409 } else {
410 sb.append(',');
411 }
412 sb.append(parameters[i].getName());
413 }
414
415 return sb.toString();
416 }
417
418
419
420
421
422
423
424
425
426 private static String dropCase(String name) {
427 if (name.startsWith("is")) {
428 name = name.substring(2);
429 } else if (name.startsWith("get") || name.startsWith("set")) {
430 name = name.substring(3);
431 } else {
432 throw new ProbeException("Error parsing property name '" + name + "'. Didn't start with 'is', 'get' or 'set'.");
433 }
434
435 if (name.length() == 1 || name.length() > 1 && !Character.isUpperCase(name.charAt(1))) {
436 name = name.substring(0, 1).toLowerCase(Locale.US) + name.substring(1);
437 }
438
439 return name;
440 }
441
442
443
444
445
446
447 private static boolean canAccessPrivateMethods() {
448 try {
449 SecurityManager securityManager = System.getSecurityManager();
450 if (null != securityManager) {
451 securityManager.checkPermission(new ReflectPermission("suppressAccessChecks"));
452 }
453 } catch (SecurityException e) {
454 return false;
455 }
456 return true;
457 }
458
459
460
461
462
463
464 public String getClassName() {
465 return className;
466 }
467
468
469
470
471
472
473 public Object instantiateClass() {
474 if (defaultConstructor == null) {
475 throw new RuntimeException("Error instantiating class. There is no default constructor for class " + className);
476 }
477 try {
478 return defaultConstructor.newInstance();
479 } catch (Exception e) {
480 throw new RuntimeException("Error instantiating class. Cause: " + e, e);
481 }
482 }
483
484
485
486
487
488
489
490
491
492 public Method getSetter(String propertyName) {
493 Invoker method = (Invoker) setMethods.get(propertyName);
494 if (method == null) {
495 throw new ProbeException(
496 "There is no WRITEABLE property named '" + propertyName + "' in class '" + className + "'");
497 }
498 if (!(method instanceof MethodInvoker)) {
499 throw new ProbeException(
500 "Can't get setter method because '" + propertyName + "' is a field in class '" + className + "'");
501 }
502 return ((MethodInvoker) method).getMethod();
503 }
504
505
506
507
508
509
510
511
512
513 public Method getGetter(String propertyName) {
514 Invoker method = (Invoker) getMethods.get(propertyName);
515 if (method == null) {
516 throw new ProbeException(
517 "There is no READABLE property named '" + propertyName + "' in class '" + className + "'");
518 }
519 if (!(method instanceof MethodInvoker)) {
520 throw new ProbeException(
521 "Can't get getter method because '" + propertyName + "' is a field in class '" + className + "'");
522 }
523 return ((MethodInvoker) method).getMethod();
524 }
525
526
527
528
529
530
531
532
533
534 public Invoker getSetInvoker(String propertyName) {
535 Invoker method = (Invoker) setMethods.get(propertyName);
536 if (method == null) {
537 throw new ProbeException(
538 "There is no WRITEABLE property named '" + propertyName + "' in class '" + className + "'");
539 }
540 return method;
541 }
542
543
544
545
546
547
548
549
550
551 public Invoker getGetInvoker(String propertyName) {
552 Invoker method = (Invoker) getMethods.get(propertyName);
553 if (method == null) {
554 throw new ProbeException(
555 "There is no READABLE property named '" + propertyName + "' in class '" + className + "'");
556 }
557 return method;
558 }
559
560
561
562
563
564
565
566
567
568 public Class getSetterType(String propertyName) {
569 Class clazz = (Class) setTypes.get(propertyName);
570 if (clazz == null) {
571 throw new ProbeException(
572 "There is no WRITEABLE property named '" + propertyName + "' in class '" + className + "'");
573 }
574 return clazz;
575 }
576
577
578
579
580
581
582
583
584
585 public Class getGetterType(String propertyName) {
586 Class clazz = (Class) getTypes.get(propertyName);
587 if (clazz == null) {
588 throw new ProbeException(
589 "There is no READABLE property named '" + propertyName + "' in class '" + className + "'");
590 }
591 return clazz;
592 }
593
594
595
596
597
598
599 public String[] getReadablePropertyNames() {
600 return readablePropertyNames;
601 }
602
603
604
605
606
607
608 public String[] getWriteablePropertyNames() {
609 return writeablePropertyNames;
610 }
611
612
613
614
615
616
617
618
619
620 public boolean hasWritableProperty(String propertyName) {
621 return setMethods.containsKey(propertyName);
622 }
623
624
625
626
627
628
629
630
631
632 public boolean hasReadableProperty(String propertyName) {
633 return getMethods.containsKey(propertyName);
634 }
635
636
637
638
639
640
641
642
643
644 public static boolean isKnownType(Class clazz) {
645 return SIMPLE_TYPE_SET.contains(clazz) || Collection.class.isAssignableFrom(clazz)
646 || Map.class.isAssignableFrom(clazz) || List.class.isAssignableFrom(clazz) || Set.class.isAssignableFrom(clazz)
647 || Iterator.class.isAssignableFrom(clazz);
648 }
649
650
651
652
653
654
655
656
657
658 public static ClassInfo getInstance(Class clazz) {
659 if (!cacheEnabled) {
660 return new ClassInfo(clazz);
661 }
662 ClassInfo cached = CLASS_INFO_MAP.get(clazz);
663 if (cached == null) {
664 cached = new ClassInfo(clazz);
665 CLASS_INFO_MAP.put(clazz, cached);
666 }
667 return cached;
668 }
669
670
671
672
673
674
675
676 public static void setCacheEnabled(boolean cacheEnabled) {
677 ClassInfo.cacheEnabled = cacheEnabled;
678 }
679
680
681
682
683
684
685
686
687
688 public static Throwable unwrapThrowable(Throwable t) {
689 Throwable t2 = t;
690 while (true) {
691 if (t2 instanceof InvocationTargetException) {
692 t2 = ((InvocationTargetException) t).getTargetException();
693 } else if (t2 instanceof UndeclaredThrowableException) {
694 t2 = ((UndeclaredThrowableException) t).getUndeclaredThrowable();
695 } else {
696 return t2;
697 }
698 }
699 }
700
701 }