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