1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.kuali.rice.krad.uif.util;
17
18 import java.beans.BeanInfo;
19 import java.beans.IntrospectionException;
20 import java.beans.Introspector;
21 import java.beans.PropertyDescriptor;
22 import java.beans.PropertyEditor;
23 import java.beans.PropertyEditorManager;
24 import java.lang.annotation.Annotation;
25 import java.lang.reflect.Field;
26 import java.lang.reflect.Method;
27 import java.lang.reflect.Modifier;
28 import java.lang.reflect.ParameterizedType;
29 import java.lang.reflect.Type;
30 import java.lang.reflect.WildcardType;
31 import java.util.ArrayList;
32 import java.util.Collection;
33 import java.util.Collections;
34 import java.util.HashSet;
35 import java.util.LinkedHashMap;
36 import java.util.LinkedHashSet;
37 import java.util.LinkedList;
38 import java.util.List;
39 import java.util.Map;
40 import java.util.Map.Entry;
41 import java.util.Queue;
42 import java.util.Set;
43 import java.util.WeakHashMap;
44
45 import org.apache.commons.lang.StringUtils;
46 import org.apache.log4j.Logger;
47 import org.kuali.rice.krad.service.DataDictionaryService;
48 import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
49 import org.kuali.rice.krad.uif.UifConstants;
50 import org.kuali.rice.krad.uif.lifecycle.ViewPostMetadata;
51 import org.kuali.rice.krad.uif.util.ObjectPathExpressionParser.PathEntry;
52 import org.kuali.rice.krad.uif.view.ViewModel;
53 import org.springframework.beans.BeanWrapper;
54 import org.springframework.beans.PropertyEditorRegistry;
55 import org.springframework.web.context.request.RequestAttributes;
56 import org.springframework.web.context.request.RequestContextHolder;
57
58
59
60
61
62
63 public final class ObjectPropertyUtils {
64 private static final Logger LOG = Logger.getLogger(ObjectPropertyUtils.class);
65
66
67 private static final boolean isJdk6 = System.getProperty("java.version").startsWith("1.6.");
68
69
70
71
72
73
74
75
76
77
78
79
80 private static final Map<Class<?>, ObjectPropertyMetadata> METADATA_CACHE = Collections
81 .synchronizedMap(new WeakHashMap<Class<?>, ObjectPropertyMetadata>(2048));
82
83
84
85
86
87
88
89 public static Map<String, PropertyDescriptor> getPropertyDescriptors(Class<?> beanClass) {
90 return getMetadata(beanClass).propertyDescriptors;
91 }
92
93
94
95
96
97
98
99
100 public static PropertyDescriptor getPropertyDescriptor(Class<?> beanClass, String propertyName) {
101 if (propertyName == null) {
102 throw new IllegalArgumentException("Null property name");
103 }
104
105 PropertyDescriptor propertyDescriptor = getPropertyDescriptors(beanClass).get(propertyName);
106 if (propertyDescriptor != null) {
107 return propertyDescriptor;
108 } else {
109 throw new IllegalArgumentException("Property " + propertyName
110 + " not found for bean " + beanClass);
111 }
112 }
113
114
115
116
117
118
119 public static void registerPropertyEditors(PropertyEditorRegistry registry) {
120 DataDictionaryService dataDictionaryService = KRADServiceLocatorWeb.getDataDictionaryService();
121 Map<Class<?>, String> propertyEditorMap = dataDictionaryService.getPropertyEditorMap();
122
123 if (propertyEditorMap == null) {
124 LOG.warn("No propertyEditorMap defined in data dictionary");
125 return;
126 }
127
128 for (Entry<Class<?>, String> propertyEditorEntry : propertyEditorMap.entrySet()) {
129 PropertyEditor editor = (PropertyEditor) dataDictionaryService.getDataDictionary().getDictionaryPrototype(
130 propertyEditorEntry.getValue());
131 registry.registerCustomEditor(propertyEditorEntry.getKey(), editor);
132
133 if (LOG.isDebugEnabled()) {
134 LOG.debug("registered " + propertyEditorEntry);
135 }
136 }
137 }
138
139
140
141
142
143
144
145 public static Set<String> getReadablePropertyNames(Class<?> beanClass) {
146 return getMetadata(beanClass).readMethods.keySet();
147 }
148
149
150
151
152
153
154
155
156 public static Method getReadMethod(Class<?> beanClass, String propertyName) {
157 return getMetadata(beanClass).readMethods.get(propertyName);
158 }
159
160
161
162
163
164
165
166
167 public static Method getWriteMethod(Class<?> beanClass, String propertyName) {
168 return getMetadata(beanClass).writeMethods.get(propertyName);
169 }
170
171
172
173
174
175
176
177
178
179
180 public static void copyPropertiesToObject(Map<String, String> properties, Object object) {
181 for (Map.Entry<String, String> property : properties.entrySet()) {
182 setPropertyValue(object, property.getKey(), property.getValue());
183 }
184 }
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203 public static Class<?> getPropertyType(Class<?> beanClass, String propertyPath) {
204 try {
205 ObjectPropertyReference.setWarning(true);
206 return ObjectPropertyReference.resolvePath(null, beanClass, propertyPath, false).getPropertyType();
207 } finally {
208 ObjectPropertyReference.setWarning(false);
209 }
210 }
211
212
213
214
215
216
217
218
219
220
221 public static Class<?> getPropertyType(Object object, String propertyPath) {
222 try {
223 ObjectPropertyReference.setWarning(true);
224 return ObjectPropertyReference.resolvePath(object, object.getClass(), propertyPath, false)
225 .getPropertyType();
226 } finally {
227 ObjectPropertyReference.setWarning(false);
228 }
229 }
230
231
232
233
234
235
236
237
238 public static Set<String> getReadablePropertyNamesByType(
239 Object bean, Class<?> propertyType) {
240 return getReadablePropertyNamesByType(bean.getClass(), propertyType);
241 }
242
243
244
245
246
247
248
249
250 public static Set<String> getReadablePropertyNamesByType(
251 Class<?> beanClass, Class<?> propertyType) {
252 return getMetadata(beanClass).getReadablePropertyNamesByType(propertyType);
253 }
254
255
256
257
258
259
260
261
262 public static Set<String> getReadablePropertyNamesByAnnotationType(
263 Object bean, Class<? extends Annotation> annotationType) {
264 return getReadablePropertyNamesByAnnotationType(bean.getClass(), annotationType);
265 }
266
267
268
269
270
271
272
273
274 public static Set<String> getReadablePropertyNamesByAnnotationType(
275 Class<?> beanClass, Class<? extends Annotation> annotationType) {
276 return getMetadata(beanClass).getReadablePropertyNamesByAnnotationType(annotationType);
277 }
278
279
280
281
282
283
284
285
286 public static Set<String> getReadablePropertyNamesByCollectionType(
287 Object bean, Class<?> collectionType) {
288 return getReadablePropertyNamesByCollectionType(bean.getClass(), collectionType);
289 }
290
291
292
293
294
295
296
297 public static Set<String> getWritablePropertyNames(Object bean) {
298 return getMetadata(bean.getClass()).getWritablePropertyNames();
299 }
300
301
302
303
304
305
306
307
308 public static Set<String> getReadablePropertyNamesByCollectionType(
309 Class<?> beanClass, Class<?> collectionType) {
310 return getMetadata(beanClass).getReadablePropertyNamesByCollectionType(collectionType);
311 }
312
313
314
315
316
317
318
319
320
321
322 @SuppressWarnings("unchecked")
323 public static <T extends Object> T getPropertyValue(Object object, String propertyPath) {
324 boolean trace = ProcessLogger.isTraceActive() && object != null;
325 if (trace) {
326
327
328 ProcessLogger.countBegin("bean-property-read");
329 }
330
331 try {
332 ObjectPropertyReference.setWarning(true);
333
334 return (T) ObjectPropertyReference.resolvePath(object, object.getClass(), propertyPath, false).get();
335
336 } catch (RuntimeException e) {
337 throw new IllegalArgumentException("Error getting property '" + propertyPath + "' from " + object, e);
338 } finally {
339 ObjectPropertyReference.setWarning(false);
340 if (trace) {
341 ProcessLogger.countEnd("bean-property-read", object.getClass().getSimpleName() + ":" + propertyPath);
342 }
343 }
344
345 }
346
347
348
349
350
351
352
353
354 public static String getPropertyValueAsText(Object bean, String path) {
355 Object propertyValue = getPropertyValue(bean, path);
356 PropertyEditor editor = getPropertyEditor(bean, path);
357
358 if (editor == null) {
359 return propertyValue == null ? null : propertyValue.toString();
360 } else {
361 editor.setValue(propertyValue);
362 return editor.getAsText();
363 }
364 }
365
366
367
368
369 public static PropertyEditorRegistry getPropertyEditorRegistry() {
370 RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
371
372 PropertyEditorRegistry registry = null;
373 if (attributes != null) {
374 registry = (PropertyEditorRegistry) attributes
375 .getAttribute(UifConstants.PROPERTY_EDITOR_REGISTRY, RequestAttributes.SCOPE_REQUEST);
376 }
377
378 return registry;
379 }
380
381
382
383
384
385
386
387 public static Class<?> getPrimitiveType(Class<?> type) {
388 if (Byte.class.equals(type)) {
389 return Byte.TYPE;
390
391 } else if (Short.class.equals(type)) {
392 return Short.TYPE;
393
394 } else if (Integer.class.equals(type)) {
395 return Integer.TYPE;
396
397 } else if (Long.class.equals(type)) {
398 return Long.TYPE;
399
400 } else if (Boolean.class.equals(type)) {
401 return Boolean.TYPE;
402
403 } else if (Float.class.equals(type)) {
404 return Float.TYPE;
405
406 } else if (Double.class.equals(type)) {
407 return Double.TYPE;
408 }
409
410 return type;
411 }
412
413
414
415
416
417
418
419
420 public static PropertyEditor getPropertyEditor(Object bean, String path) {
421 Class<?> propertyType = getPrimitiveType(getPropertyType(bean, path));
422
423 PropertyEditor editor = null;
424
425 PropertyEditorRegistry registry = getPropertyEditorRegistry();
426 if (registry != null) {
427 editor = registry.findCustomEditor(propertyType, path);
428
429 if (editor != null && editor != registry.findCustomEditor(propertyType, null)) {
430 return editor;
431 }
432
433 if (registry instanceof BeanWrapper
434 && bean == ((BeanWrapper) registry).getWrappedInstance()
435 && (bean instanceof ViewModel)) {
436
437 ViewModel viewModel = (ViewModel) bean;
438 ViewPostMetadata viewPostMetadata = viewModel.getViewPostMetadata();
439 PropertyEditor editorFromView = viewPostMetadata == null ? null : viewPostMetadata.getFieldEditor(path);
440
441 if (editorFromView != null) {
442 registry.registerCustomEditor(propertyType, path, editorFromView);
443 editor = registry.findCustomEditor(propertyType, path);
444 }
445 }
446 }
447
448 if (editor != null) {
449 return editor;
450 }
451
452 return getPropertyEditor(propertyType);
453 }
454
455
456
457
458
459
460
461
462 public static PropertyEditor getPropertyEditor(Class<?> propertyType) {
463 PropertyEditorRegistry registry = getPropertyEditorRegistry();
464 PropertyEditor editor = null;
465
466 if (registry != null) {
467 editor = registry.findCustomEditor(propertyType, null);
468 } else {
469
470 DataDictionaryService dataDictionaryService = KRADServiceLocatorWeb.getDataDictionaryService();
471 Map<Class<?>, String> editorMap = dataDictionaryService.getPropertyEditorMap();
472 String editorPrototypeName = editorMap == null ? null : editorMap.get(propertyType);
473
474 if (editorPrototypeName != null) {
475 editor = (PropertyEditor) dataDictionaryService.getDataDictionary().getDictionaryPrototype(editorPrototypeName);
476 }
477 }
478
479 if (editor == null && propertyType != null) {
480
481 editor = PropertyEditorManager.findEditor(propertyType);
482 }
483
484 return editor;
485 }
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501 public static void initializeProperty(Object object, String propertyPath) {
502 Class<?> propertyType = getPropertyType(object, propertyPath);
503 try {
504 setPropertyValue(object, propertyPath, propertyType.newInstance());
505 } catch (InstantiationException e) {
506
507 setPropertyValue(object, propertyPath, null);
508 } catch (IllegalAccessException e) {
509 throw new IllegalArgumentException("Unable to set new instance for property: " + propertyPath, e);
510 }
511 }
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531 public static void setPropertyValue(Object object, String propertyPath, Object propertyValue) {
532 if (ProcessLogger.isTraceActive() && object != null) {
533
534
535 ProcessLogger.countBegin("bean-property-write");
536 }
537
538 try {
539 ObjectPropertyReference.setWarning(true);
540
541 ObjectPropertyReference.resolvePath(object, object.getClass(), propertyPath, true).set(propertyValue);
542
543 } catch (RuntimeException e) {
544 throw new IllegalArgumentException(
545 "Error setting property '" + propertyPath + "' on " + object + " with " + propertyValue, e);
546 } finally {
547 ObjectPropertyReference.setWarning(false);
548
549 if (ProcessLogger.isTraceActive() && object != null) {
550 ProcessLogger.countEnd("bean-property-write", object.getClass().getSimpleName() + ":" + propertyPath);
551 }
552 }
553
554 }
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574 public static void setPropertyValue(Object object, String propertyPath, Object propertyValue, boolean ignoreUnknown) {
575 try {
576 setPropertyValue(object, propertyPath, propertyValue);
577 } catch (RuntimeException e) {
578
579 if (!ignoreUnknown) {
580 throw e;
581 }
582 if (LOG.isTraceEnabled()) {
583 LOG.trace("Ignoring exception thrown during setting of property '" + propertyPath + "': "
584 + e.getLocalizedMessage());
585 }
586 }
587 }
588
589
590
591
592
593
594
595
596
597 public static boolean isReadableProperty(Object object, String propertyPath) {
598 return ObjectPropertyReference.resolvePath(object, object.getClass(), propertyPath, false).canRead();
599 }
600
601
602
603
604
605
606
607
608
609 public static boolean isWritableProperty(Object object, String propertyPath) {
610 return ObjectPropertyReference.resolvePath(object, object.getClass(), propertyPath, false).canWrite();
611 }
612
613
614
615
616
617
618
619
620
621
622
623
624 public static List<Field> getAllFields(List<Field> fields, Class<?> type, Class<?> stopAt) {
625 for (Field field : type.getDeclaredFields()) {
626 fields.add(field);
627 }
628
629 if (type.getSuperclass() != null && !type.getName().equals(stopAt.getName())) {
630 fields = getAllFields(fields, type.getSuperclass(), stopAt);
631 }
632
633 return fields;
634 }
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652 public static Type getComponentType(Type type) {
653 if (!(type instanceof ParameterizedType)) {
654 return Object.class;
655 }
656
657 ParameterizedType parameterizedType = (ParameterizedType) type;
658 Type[] params = parameterizedType.getActualTypeArguments();
659 if (params.length == 0) {
660 return Object.class;
661 }
662
663 Type valueType = params[params.length - 1];
664 return valueType;
665 }
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686 public static Class<?> getUpperBound(Type valueType) {
687 if (valueType instanceof WildcardType) {
688 Type[] upperBounds = ((WildcardType) valueType).getUpperBounds();
689
690 if (upperBounds.length >= 1) {
691 valueType = upperBounds[0];
692 }
693 }
694
695 if (valueType instanceof ParameterizedType) {
696 valueType = ((ParameterizedType) valueType).getRawType();
697 }
698
699 if (valueType instanceof Class) {
700 return (Class<?>) valueType;
701 }
702
703 return Object.class;
704 }
705
706
707
708
709
710
711
712
713
714
715
716 public static Type findGenericType(Class<?> sourceClass, Class<?> targetClass) {
717 if (!targetClass.isAssignableFrom(sourceClass)) {
718 throw new IllegalArgumentException(targetClass + " is not assignable from " + sourceClass);
719 }
720
721 if (sourceClass.equals(targetClass)) {
722 return sourceClass;
723 }
724
725 @SuppressWarnings("unchecked")
726 Queue<Type> typeQueue = RecycleUtils.getInstance(LinkedList.class);
727 typeQueue.offer(sourceClass);
728 while (!typeQueue.isEmpty()) {
729 Type type = typeQueue.poll();
730
731 Class<?> upperBound = getUpperBound(type);
732 if (targetClass.equals(upperBound)) {
733 return type;
734 }
735
736 Type genericSuper = upperBound.getGenericSuperclass();
737 if (genericSuper != null) {
738 typeQueue.offer(genericSuper);
739 }
740
741 Type[] genericInterfaces = upperBound.getGenericInterfaces();
742 for (int i=0; i<genericInterfaces.length; i++) {
743 if (genericInterfaces[i] != null) {
744 typeQueue.offer(genericInterfaces[i]);
745 }
746 }
747 }
748
749 throw new IllegalStateException(targetClass + " is assignable from " + sourceClass
750 + " but could not be found in the generic type hierarchy");
751 }
752
753
754
755
756
757
758 private static class SplitPropertyPathEntry implements PathEntry {
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778 @Override
779 public List<String> parse(String parentPath, Object node, String next) {
780 if (next == null) {
781 return new ArrayList<String>();
782 }
783
784 @SuppressWarnings("unchecked")
785 List<String> rv = (List<String>) node;
786
787 if (rv.isEmpty()) {
788 rv.add(next);
789 return rv;
790 }
791
792 rejoinTrailingIndexReference(rv, parentPath);
793 rv.add(next);
794
795 return rv;
796 }
797 }
798
799 private static final SplitPropertyPathEntry SPLIT_PROPERTY_PATH_ENTRY = new SplitPropertyPathEntry();
800 private static final String[] EMPTY_STRING_ARRAY = new String[0];
801
802
803
804
805
806
807
808
809
810
811
812
813 private static void rejoinTrailingIndexReference(List<String> tokenList, String path) {
814 int lastIndex = tokenList.size() - 1;
815 String lastToken = tokenList.get(lastIndex);
816 String lastParentToken = path.substring(path.lastIndexOf('.') + 1);
817
818 if (!lastToken.equals(lastParentToken) && lastIndex > 0) {
819
820
821
822
823 String prevToken = tokenList.get(--lastIndex);
824
825
826 int iopt = path.lastIndexOf(prevToken, path.lastIndexOf(lastToken));
827
828 String fullToken = path.substring(iopt);
829 tokenList.remove(lastIndex);
830 tokenList.set(lastIndex, fullToken);
831 }
832 }
833
834
835
836
837
838
839
840
841 public static String[] splitPropertyPath(String path) {
842 List<String> split = ObjectPathExpressionParser.parsePathExpression(null, path, SPLIT_PROPERTY_PATH_ENTRY);
843 if (split == null || split.isEmpty()) {
844 return EMPTY_STRING_ARRAY;
845 }
846
847 rejoinTrailingIndexReference(split, path);
848
849 return split.toArray(new String[split.size()]);
850 }
851
852
853
854
855
856
857
858
859
860
861 public static String getPathTail(String path) {
862 String[] propertyPaths = splitPropertyPath(path);
863
864 return propertyPaths[propertyPaths.length - 1];
865 }
866
867
868
869
870
871
872
873
874
875
876 public static String removePathTail(String path) {
877 String[] propertyPaths = splitPropertyPath(path);
878
879 return StringUtils.join(propertyPaths, ".", 0, propertyPaths.length - 1);
880 }
881
882
883
884
885
886
887
888 public static String getCanonicalPath(String path) {
889 if (path == null || path.indexOf('[') == -1) {
890 return path;
891 }
892
893
894
895 StringBuilder pathBuilder = new StringBuilder(path);
896
897 int bracketCount = 0;
898 int leftBracketPos = -1;
899 for (int i = 0; i < pathBuilder.length(); i++) {
900 char c = pathBuilder.charAt(i);
901
902 if (c == '[') {
903 bracketCount++;
904 if (bracketCount == 1)
905 leftBracketPos = i;
906 }
907
908 if (c == ']') {
909 bracketCount--;
910
911 if (bracketCount < 0) {
912 throw new IllegalArgumentException("Unmatched ']' at " + i + " " + pathBuilder);
913 }
914
915 if (bracketCount == 0) {
916 pathBuilder.delete(leftBracketPos, i + 1);
917 i -= i + 1 - leftBracketPos;
918 leftBracketPos = -1;
919 }
920 }
921 }
922
923 if (bracketCount > 0) {
924 throw new IllegalArgumentException("Unmatched '[' at " + leftBracketPos + " " + pathBuilder);
925 }
926
927 return pathBuilder.toString();
928 }
929
930
931
932
933 private ObjectPropertyUtils() {}
934
935
936
937
938
939
940
941
942 private static Method getReadMethodByName(Class<?> beanClass, String propertyName) {
943
944 try {
945 return beanClass.getMethod("get" + Character.toUpperCase(propertyName.charAt(0))
946 + propertyName.substring(1));
947 } catch (SecurityException e) {
948
949 } catch (NoSuchMethodException e) {
950
951 }
952
953 try {
954 Method readMethod = beanClass.getMethod("is"
955 + Character.toUpperCase(propertyName.charAt(0))
956 + propertyName.substring(1));
957
958 if (readMethod.getReturnType() == Boolean.class
959 || readMethod.getReturnType() == Boolean.TYPE) {
960 return readMethod;
961 }
962 } catch (SecurityException e) {
963
964 } catch (NoSuchMethodException e) {
965
966 }
967
968 return null;
969 }
970
971
972
973
974
975
976
977 private static ObjectPropertyMetadata getMetadata(Class<?> beanClass) {
978 ObjectPropertyMetadata metadata = METADATA_CACHE.get(beanClass);
979
980 if (metadata == null) {
981 metadata = new ObjectPropertyMetadata(beanClass);
982 METADATA_CACHE.put(beanClass, metadata);
983 }
984
985 return metadata;
986 }
987
988
989
990
991
992
993
994 private static class ObjectPropertyMetadata {
995
996 private final Map<String, PropertyDescriptor> propertyDescriptors;
997 private final Map<String, Method> readMethods;
998 private final Map<String, Method> writeMethods;
999 private final Map<Class<?>, Set<String>> readablePropertyNamesByPropertyType =
1000 Collections.synchronizedMap(new WeakHashMap<Class<?>, Set<String>>());
1001 private final Map<Class<?>, Set<String>> readablePropertyNamesByAnnotationType =
1002 Collections.synchronizedMap(new WeakHashMap<Class<?>, Set<String>>());
1003 private final Map<Class<?>, Set<String>> readablePropertyNamesByCollectionType =
1004 Collections.synchronizedMap(new WeakHashMap<Class<?>, Set<String>>());
1005
1006
1007
1008
1009
1010
1011
1012 private Set<String> getReadablePropertyNamesByType(Class<?> propertyType) {
1013 Set<String> propertyNames = readablePropertyNamesByPropertyType.get(propertyType);
1014 if (propertyNames != null) {
1015 return propertyNames;
1016 }
1017
1018 propertyNames = new LinkedHashSet<String>();
1019 for (Entry<String, Method> readMethodEntry : readMethods.entrySet()) {
1020 Method readMethod = readMethodEntry.getValue();
1021 if (readMethod != null && propertyType.isAssignableFrom(readMethod.getReturnType())) {
1022 propertyNames.add(readMethodEntry.getKey());
1023 }
1024 }
1025
1026 propertyNames = Collections.unmodifiableSet(propertyNames);
1027 readablePropertyNamesByPropertyType.put(propertyType, propertyNames);
1028
1029 return propertyNames;
1030 }
1031
1032
1033
1034
1035
1036
1037
1038 private Set<String> getReadablePropertyNamesByAnnotationType(
1039 Class<? extends Annotation> annotationType) {
1040 Set<String> propertyNames = readablePropertyNamesByAnnotationType.get(annotationType);
1041 if (propertyNames != null) {
1042 return propertyNames;
1043 }
1044
1045 propertyNames = new LinkedHashSet<String>();
1046 for (Entry<String, Method> readMethodEntry : readMethods.entrySet()) {
1047 Method readMethod = readMethodEntry.getValue();
1048 if (readMethod != null && readMethod.isAnnotationPresent(annotationType)) {
1049 propertyNames.add(readMethodEntry.getKey());
1050 }
1051 }
1052
1053 propertyNames = Collections.unmodifiableSet(propertyNames);
1054 readablePropertyNamesByPropertyType.put(annotationType, propertyNames);
1055
1056 return propertyNames;
1057 }
1058
1059
1060
1061
1062
1063
1064
1065 private Set<String> getReadablePropertyNamesByCollectionType(Class<?> collectionType) {
1066 Set<String> propertyNames = readablePropertyNamesByCollectionType.get(collectionType);
1067 if (propertyNames != null) {
1068 return propertyNames;
1069 }
1070
1071 propertyNames = new LinkedHashSet<String>();
1072 for (Entry<String, Method> readMethodEntry : readMethods.entrySet()) {
1073 Method readMethod = readMethodEntry.getValue();
1074 if (readMethod == null) {
1075 continue;
1076 }
1077
1078 Class<?> propertyClass = readMethod.getReturnType();
1079 if (propertyClass.isArray() &&
1080 collectionType.isAssignableFrom(propertyClass.getComponentType())) {
1081 propertyNames.add(readMethodEntry.getKey());
1082 continue;
1083 }
1084
1085 boolean isCollection = Collection.class.isAssignableFrom(propertyClass);
1086 boolean isMap = Map.class.isAssignableFrom(propertyClass);
1087 if (!isCollection && !isMap) {
1088 continue;
1089 }
1090
1091 if (collectionType.equals(Object.class)) {
1092 propertyNames.add(readMethodEntry.getKey());
1093 continue;
1094 }
1095
1096 Type propertyType = readMethodEntry.getValue().getGenericReturnType();
1097 if (propertyType instanceof ParameterizedType) {
1098 ParameterizedType parameterizedType = (ParameterizedType) propertyType;
1099 Type valueType = parameterizedType.getActualTypeArguments()[isCollection ? 0 : 1];
1100
1101 if (valueType instanceof WildcardType) {
1102 Type[] upperBounds = ((WildcardType) valueType).getUpperBounds();
1103
1104 if (upperBounds.length >= 1) {
1105 valueType = upperBounds[0];
1106 }
1107 }
1108
1109 if (valueType instanceof Class &&
1110 collectionType.isAssignableFrom((Class<?>) valueType)) {
1111 propertyNames.add(readMethodEntry.getKey());
1112 }
1113 }
1114 }
1115
1116 propertyNames = Collections.unmodifiableSet(propertyNames);
1117 readablePropertyNamesByCollectionType.put(collectionType, propertyNames);
1118
1119 return propertyNames;
1120 }
1121
1122
1123
1124
1125
1126
1127 private Set<String> getWritablePropertyNames() {
1128 Set<String> writablePropertyNames = new HashSet<String>();
1129
1130 for (Entry<String, Method> writeMethodEntry : writeMethods.entrySet()) {
1131 writablePropertyNames.add(writeMethodEntry.getKey());
1132 }
1133
1134 return writablePropertyNames;
1135 }
1136
1137
1138
1139
1140
1141
1142 private ObjectPropertyMetadata(Class<?> beanClass) {
1143 if (beanClass == null) {
1144 throw new RuntimeException("Class to retrieve property from was null");
1145 }
1146
1147 BeanInfo beanInfo;
1148 try {
1149 beanInfo = Introspector.getBeanInfo(beanClass);
1150 } catch (IntrospectionException e) {
1151 LOG.warn(
1152 "Bean Info not found for bean " + beanClass, e);
1153 beanInfo = null;
1154 }
1155
1156 Map<String, PropertyDescriptor> mutablePropertyDescriptorMap = new LinkedHashMap<String, PropertyDescriptor>();
1157 Map<String, Method> mutableReadMethodMap = new LinkedHashMap<String, Method>();
1158 Map<String, Method> mutableWriteMethodMap = new LinkedHashMap<String, Method>();
1159
1160 if (beanInfo != null) {
1161 for (PropertyDescriptor propertyDescriptor : beanInfo
1162 .getPropertyDescriptors()) {
1163 String propertyName = propertyDescriptor.getName();
1164
1165 mutablePropertyDescriptorMap.put(propertyName, propertyDescriptor);
1166 Method readMethod = propertyDescriptor.getReadMethod();
1167 if (readMethod == null) {
1168 readMethod = getReadMethodByName(beanClass, propertyName);
1169 }
1170
1171
1172 if (isJdk6) {
1173 readMethod = getCorrectedReadMethod(beanClass, readMethod);
1174 }
1175
1176 mutableReadMethodMap.put(propertyName, readMethod);
1177
1178 Method writeMethod = propertyDescriptor.getWriteMethod();
1179 assert writeMethod == null
1180 || (writeMethod.getParameterTypes().length == 1 && writeMethod.getParameterTypes()[0] != null) : writeMethod;
1181 mutableWriteMethodMap.put(propertyName, writeMethod);
1182 }
1183 }
1184
1185
1186
1187 propertyDescriptors = Collections.unmodifiableMap(mutablePropertyDescriptorMap);
1188 readMethods = Collections.unmodifiableMap(mutableReadMethodMap);
1189 writeMethods = Collections.unmodifiableMap(mutableWriteMethodMap);
1190 }
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204 private Method getCorrectedReadMethod(Class<?> beanClass, Method readMethod) {
1205 if (readMethod != null && !readMethod.getReturnType().isPrimitive() &&
1206 isAbstractClassOrInterface(readMethod.getReturnType())) {
1207
1208 Method implReadMethod = null;
1209
1210 try {
1211 implReadMethod = beanClass.getMethod(readMethod.getName(), readMethod.getParameterTypes());
1212 } catch (NoSuchMethodException e) {
1213
1214 }
1215
1216 if (implReadMethod != null && isSubClass(implReadMethod.getReturnType(), readMethod.getReturnType())) {
1217 return implReadMethod;
1218 }
1219 }
1220
1221 return readMethod;
1222 }
1223
1224
1225 private boolean isAbstractClassOrInterface(Class<?> clazz) {
1226 return clazz.isInterface() || Modifier.isAbstract(clazz.getModifiers());
1227 }
1228
1229
1230 private boolean isSubClass(Class<?> childClassCandidate, Class<?> parentClassCandidate) {
1231
1232 return parentClassCandidate != childClassCandidate &&
1233 parentClassCandidate.isAssignableFrom(childClassCandidate);
1234 }
1235 }
1236 }