1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.commons.beanutils2;
19
20 import java.beans.IndexedPropertyDescriptor;
21 import java.beans.IntrospectionException;
22 import java.beans.Introspector;
23 import java.beans.PropertyDescriptor;
24 import java.lang.reflect.Array;
25 import java.lang.reflect.InvocationTargetException;
26 import java.lang.reflect.Method;
27 import java.util.HashMap;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Objects;
31 import java.util.concurrent.ConcurrentHashMap;
32 import java.util.concurrent.CopyOnWriteArrayList;
33
34 import org.apache.commons.beanutils2.expression.DefaultResolver;
35 import org.apache.commons.beanutils2.expression.Resolver;
36 import org.apache.commons.logging.Log;
37 import org.apache.commons.logging.LogFactory;
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70 public class PropertyUtilsBean {
71
72
73 private static final Log LOG = LogFactory.getLog(PropertyUtilsBean.class);
74
75
76
77
78
79
80 protected static PropertyUtilsBean getInstance() {
81 return BeanUtilsBean.getInstance().getPropertyUtils();
82 }
83
84
85
86
87
88
89
90
91 @SuppressWarnings("unchecked")
92 private static List<Object> toObjectList(final Object obj) {
93
94 return (List<Object>) obj;
95 }
96
97
98
99
100
101
102
103
104 @SuppressWarnings("unchecked")
105 private static Map<String, Object> toPropertyMap(final Object obj) {
106
107 return (Map<String, Object>) obj;
108 }
109
110 private Resolver resolver = new DefaultResolver();
111
112
113
114
115 private final Map<Class<?>, BeanIntrospectionData> descriptorsCache;
116
117 private final Map<Class<?>, Map> mappedDescriptorsCache;
118
119
120 private final List<BeanIntrospector> introspectors;
121
122
123 public PropertyUtilsBean() {
124 descriptorsCache = BeanUtils.createCache();
125 mappedDescriptorsCache = BeanUtils.createCache();
126 introspectors = new CopyOnWriteArrayList<>();
127 resetBeanIntrospectors();
128 }
129
130
131
132
133
134
135
136
137 public void addBeanIntrospector(final BeanIntrospector introspector) {
138 introspectors.add(Objects.requireNonNull(introspector, "introspector"));
139 }
140
141
142
143
144
145 public void clearDescriptors() {
146 descriptorsCache.clear();
147 mappedDescriptorsCache.clear();
148 Introspector.flushCaches();
149 }
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175 public void copyProperties(final Object dest, final Object orig) throws IllegalAccessException, InvocationTargetException,
176
177
178 NoSuchMethodException {
179 Objects.requireNonNull(dest, "dest");
180 Objects.requireNonNull(orig, "orig");
181 if (orig instanceof DynaBean) {
182 final DynaProperty[] origDescriptors = ((DynaBean) orig).getDynaClass().getDynaProperties();
183 for (final DynaProperty origDescriptor : origDescriptors) {
184 final String name = origDescriptor.getName();
185 if (isReadable(orig, name) && isWriteable(dest, name)) {
186 try {
187 final Object value = ((DynaBean) orig).get(name);
188 if (dest instanceof DynaBean) {
189 ((DynaBean) dest).set(name, value);
190 } else {
191 setSimpleProperty(dest, name, value);
192 }
193 } catch (final NoSuchMethodException e) {
194 if (LOG.isDebugEnabled()) {
195 LOG.debug("Error writing to '" + name + "' on class '" + dest.getClass() + "'", e);
196 }
197 }
198 }
199 }
200 } else if (orig instanceof Map) {
201 for (final Map.Entry<?, ?> entry : ((Map<?, ?>) orig).entrySet()) {
202 final String name = (String) entry.getKey();
203 if (isWriteable(dest, name)) {
204 try {
205 if (dest instanceof DynaBean) {
206 ((DynaBean) dest).set(name, entry.getValue());
207 } else {
208 setSimpleProperty(dest, name, entry.getValue());
209 }
210 } catch (final NoSuchMethodException e) {
211 if (LOG.isDebugEnabled()) {
212 LOG.debug("Error writing to '" + name + "' on class '" + dest.getClass() + "'", e);
213 }
214 }
215 }
216 }
217 } else {
218 final PropertyDescriptor[] origDescriptors = getPropertyDescriptors(orig);
219 for (final PropertyDescriptor origDescriptor : origDescriptors) {
220 final String name = origDescriptor.getName();
221 if (isReadable(orig, name) && isWriteable(dest, name)) {
222 try {
223 final Object value = getSimpleProperty(orig, name);
224 if (dest instanceof DynaBean) {
225 ((DynaBean) dest).set(name, value);
226 } else {
227 setSimpleProperty(dest, name, value);
228 }
229 } catch (final NoSuchMethodException e) {
230 if (LOG.isDebugEnabled()) {
231 LOG.debug("Error writing to '" + name + "' on class '" + dest.getClass() + "'", e);
232 }
233 }
234 }
235 }
236 }
237
238 }
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257 public Map<String, Object> describe(final Object bean) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
258 Objects.requireNonNull(bean, "bean");
259 final Map<String, Object> description = new HashMap<>();
260 if (bean instanceof DynaBean) {
261 final DynaProperty[] descriptors = ((DynaBean) bean).getDynaClass().getDynaProperties();
262 for (final DynaProperty descriptor : descriptors) {
263 final String name = descriptor.getName();
264 description.put(name, getProperty(bean, name));
265 }
266 } else {
267 final PropertyDescriptor[] descriptors = getPropertyDescriptors(bean);
268 for (final PropertyDescriptor descriptor : descriptors) {
269 final String name = descriptor.getName();
270 if (descriptor.getReadMethod() != null) {
271 description.put(name, getProperty(bean, name));
272 }
273 }
274 }
275 return description;
276 }
277
278
279
280
281
282
283
284 private BeanIntrospectionData fetchIntrospectionData(final Class<?> beanClass) {
285 final DefaultIntrospectionContext ictx = new DefaultIntrospectionContext(beanClass);
286
287 for (final BeanIntrospector bi : introspectors) {
288 try {
289 bi.introspect(ictx);
290 } catch (final IntrospectionException iex) {
291 LOG.error("Exception during introspection", iex);
292 }
293 }
294
295 return new BeanIntrospectionData(ictx.getPropertyDescriptors());
296 }
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312 public Object getIndexedProperty(final Object bean, String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
313 Objects.requireNonNull(bean, "bean");
314 Objects.requireNonNull(name, "name");
315
316 int index = -1;
317 try {
318 index = resolver.getIndex(name);
319 } catch (final IllegalArgumentException e) {
320 throw new IllegalArgumentException("Invalid indexed property '" + name + "' on bean class '" + bean.getClass() + "' " + e.getMessage());
321 }
322 if (index < 0) {
323 throw new IllegalArgumentException("Invalid indexed property '" + name + "' on bean class '" + bean.getClass() + "'");
324 }
325
326
327 name = resolver.getProperty(name);
328
329
330 return getIndexedProperty(bean, name, index);
331 }
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347 public Object getIndexedProperty(final Object bean, final String name, final int index)
348 throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
349 Objects.requireNonNull(bean, "bean");
350 if (name == null || name.isEmpty()) {
351 if (bean.getClass().isArray()) {
352 return Array.get(bean, index);
353 }
354 if (bean instanceof List) {
355 return ((List<?>) bean).get(index);
356 }
357 }
358 Objects.requireNonNull(name, "name");
359
360 if (bean instanceof DynaBean) {
361 final DynaProperty descriptor = ((DynaBean) bean).getDynaClass().getDynaProperty(name);
362 if (descriptor == null) {
363 throw new NoSuchMethodException("Unknown property '" + name + "' on bean class '" + bean.getClass() + "'");
364 }
365 return ((DynaBean) bean).get(name, index);
366 }
367
368
369 final PropertyDescriptor descriptor = getPropertyDescriptor(bean, name);
370 if (descriptor == null) {
371 throw new NoSuchMethodException("Unknown property '" + name + "' on bean class '" + bean.getClass() + "'");
372 }
373
374
375 if (descriptor instanceof IndexedPropertyDescriptor) {
376 Method readMethod = ((IndexedPropertyDescriptor) descriptor).getIndexedReadMethod();
377 readMethod = MethodUtils.getAccessibleMethod(bean.getClass(), readMethod);
378 if (readMethod != null) {
379 try {
380 return invokeMethod(readMethod, bean, Integer.valueOf(index));
381 } catch (final InvocationTargetException e) {
382 if (e.getTargetException() instanceof IndexOutOfBoundsException) {
383 throw (IndexOutOfBoundsException) e.getTargetException();
384 }
385 throw e;
386 }
387 }
388 }
389
390
391 final Method readMethod = getReadMethod(bean.getClass(), descriptor);
392 if (readMethod == null) {
393 throw new NoSuchMethodException("Property '" + name + "' has no " + "getter method on bean class '" + bean.getClass() + "'");
394 }
395
396
397 final Object value = invokeMethod(readMethod, bean, BeanUtils.EMPTY_OBJECT_ARRAY);
398 if (!value.getClass().isArray()) {
399 if (!(value instanceof List)) {
400 throw new IllegalArgumentException("Property '" + name + "' is not indexed on bean class '" + bean.getClass() + "'");
401 }
402
403 return ((List<?>) value).get(index);
404 }
405
406 try {
407 return Array.get(value, index);
408 } catch (final ArrayIndexOutOfBoundsException e) {
409 throw new ArrayIndexOutOfBoundsException("Index: " + index + ", Size: " + Array.getLength(value) + " for property '" + name + "'");
410 }
411 }
412
413
414
415
416
417
418
419
420
421 private BeanIntrospectionData getIntrospectionData(final Class<?> beanClass) {
422 Objects.requireNonNull(beanClass, "beanClass");
423
424 BeanIntrospectionData data = descriptorsCache.get(beanClass);
425 if (data == null) {
426 data = fetchIntrospectionData(beanClass);
427 descriptorsCache.put(beanClass, data);
428 }
429 return data;
430 }
431
432
433
434
435
436
437
438
439
440
441
442
443 public Object getMappedProperty(final Object bean, String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
444 Objects.requireNonNull(bean, "bean");
445 Objects.requireNonNull(name, "name");
446
447 String key = null;
448 try {
449 key = resolver.getKey(name);
450 } catch (final IllegalArgumentException e) {
451 throw new IllegalArgumentException("Invalid mapped property '" + name + "' on bean class '" + bean.getClass() + "' " + e.getMessage());
452 }
453 if (key == null) {
454 throw new IllegalArgumentException("Invalid mapped property '" + name + "' on bean class '" + bean.getClass() + "'");
455 }
456
457
458 name = resolver.getProperty(name);
459
460
461 return getMappedProperty(bean, name, key);
462 }
463
464
465
466
467
468
469
470
471
472
473
474
475 public Object getMappedProperty(final Object bean, final String name, final String key)
476 throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
477 Objects.requireNonNull(bean, "bean");
478 Objects.requireNonNull(name, "name");
479 Objects.requireNonNull(key, "key");
480
481 if (bean instanceof DynaBean) {
482 final DynaProperty descriptor = ((DynaBean) bean).getDynaClass().getDynaProperty(name);
483 if (descriptor == null) {
484 throw new NoSuchMethodException("Unknown property '" + name + "'+ on bean class '" + bean.getClass() + "'");
485 }
486 return ((DynaBean) bean).get(name, key);
487 }
488
489 Object result = null;
490
491
492 final PropertyDescriptor descriptor = getPropertyDescriptor(bean, name);
493 if (descriptor == null) {
494 throw new NoSuchMethodException("Unknown property '" + name + "'+ on bean class '" + bean.getClass() + "'");
495 }
496
497 if (descriptor instanceof MappedPropertyDescriptor) {
498
499 Method readMethod = ((MappedPropertyDescriptor) descriptor).getMappedReadMethod();
500 readMethod = MethodUtils.getAccessibleMethod(bean.getClass(), readMethod);
501 if (readMethod == null) {
502 throw new NoSuchMethodException("Property '" + name + "' has no mapped getter method on bean class '" + bean.getClass() + "'");
503 }
504 result = invokeMethod(readMethod, bean, key);
505 } else {
506
507 final Method readMethod = getReadMethod(bean.getClass(), descriptor);
508 if (readMethod == null) {
509 throw new NoSuchMethodException("Property '" + name + "' has no mapped getter method on bean class '" + bean.getClass() + "'");
510 }
511 final Object invokeResult = invokeMethod(readMethod, bean, BeanUtils.EMPTY_OBJECT_ARRAY);
512
513 if (invokeResult instanceof Map) {
514 result = ((Map<?, ?>) invokeResult).get(key);
515 }
516 }
517 return result;
518 }
519
520
521
522
523
524
525
526
527
528
529
530
531
532 Map<Class<?>, Map> getMappedPropertyDescriptors(final Class<?> beanClass) {
533 if (beanClass == null) {
534 return null;
535 }
536
537 return mappedDescriptorsCache.get(beanClass);
538 }
539
540
541
542
543
544
545
546
547
548
549
550
551
552 Map getMappedPropertyDescriptors(final Object bean) {
553 if (bean == null) {
554 return null;
555 }
556 return getMappedPropertyDescriptors(bean.getClass());
557 }
558
559
560
561
562
563
564
565
566
567
568
569
570
571 public Object getNestedProperty(Object bean, String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
572 Objects.requireNonNull(bean, "bean");
573 Objects.requireNonNull(name, "name");
574
575 while (resolver.hasNested(name)) {
576 final String next = resolver.next(name);
577 Object nestedBean = null;
578 if (bean instanceof Map) {
579 nestedBean = getPropertyOfMapBean((Map<?, ?>) bean, next);
580 } else if (resolver.isMapped(next)) {
581 nestedBean = getMappedProperty(bean, next);
582 } else if (resolver.isIndexed(next)) {
583 nestedBean = getIndexedProperty(bean, next);
584 } else {
585 nestedBean = getSimpleProperty(bean, next);
586 }
587 if (nestedBean == null) {
588 throw new NestedNullException("Null property value for '" + name + "' on bean class '" + bean.getClass() + "'");
589 }
590 bean = nestedBean;
591 name = resolver.remove(name);
592 }
593
594 if (bean instanceof Map) {
595 bean = getPropertyOfMapBean((Map<?, ?>) bean, name);
596 } else if (resolver.isMapped(name)) {
597 bean = getMappedProperty(bean, name);
598 } else if (resolver.isIndexed(name)) {
599 bean = getIndexedProperty(bean, name);
600 } else {
601 bean = getSimpleProperty(bean, name);
602 }
603 return bean;
604 }
605
606
607
608
609
610
611
612
613
614
615
616
617 public Object getProperty(final Object bean, final String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
618 return getNestedProperty(bean, name);
619 }
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645 public PropertyDescriptor getPropertyDescriptor(Object bean, String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
646 Objects.requireNonNull(bean, "bean");
647 Objects.requireNonNull(name, "name");
648
649 while (resolver.hasNested(name)) {
650 final String next = resolver.next(name);
651 final Object nestedBean = getProperty(bean, next);
652 if (nestedBean == null) {
653 throw new NestedNullException("Null property value for '" + next + "' on bean class '" + bean.getClass() + "'");
654 }
655 bean = nestedBean;
656 name = resolver.remove(name);
657 }
658
659
660 name = resolver.getProperty(name);
661
662
663
664 if (name == null) {
665 return null;
666 }
667
668 final BeanIntrospectionData data = getIntrospectionData(bean.getClass());
669 PropertyDescriptor result = data.getDescriptor(name);
670 if (result != null) {
671 return result;
672 }
673
674 Map mappedDescriptors = getMappedPropertyDescriptors(bean);
675 if (mappedDescriptors == null) {
676 mappedDescriptors = new ConcurrentHashMap<Class<?>, Map>();
677 mappedDescriptorsCache.put(bean.getClass(), mappedDescriptors);
678 }
679 result = (PropertyDescriptor) mappedDescriptors.get(name);
680 if (result == null) {
681
682 try {
683 result = new MappedPropertyDescriptor(name, bean.getClass());
684 } catch (final IntrospectionException ie) {
685
686
687
688 }
689 if (result != null) {
690 mappedDescriptors.put(name, result);
691 }
692 }
693
694 return result;
695 }
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710 public PropertyDescriptor[] getPropertyDescriptors(final Class<?> beanClass) {
711 return getIntrospectionData(beanClass).getDescriptors();
712 }
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727 public PropertyDescriptor[] getPropertyDescriptors(final Object bean) {
728 Objects.requireNonNull(bean, "bean");
729 return getPropertyDescriptors(bean.getClass());
730 }
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757 public Class<?> getPropertyEditorClass(final Object bean, final String name)
758 throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
759 Objects.requireNonNull(bean, "bean");
760 Objects.requireNonNull(name, "name");
761 final PropertyDescriptor descriptor = getPropertyDescriptor(bean, name);
762 if (descriptor != null) {
763 return descriptor.getPropertyEditorClass();
764 }
765 return null;
766 }
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784 protected Object getPropertyOfMapBean(final Map<?, ?> bean, String propertyName)
785 throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
786
787 if (resolver.isMapped(propertyName)) {
788 final String name = resolver.getProperty(propertyName);
789 if (name == null || name.isEmpty()) {
790 propertyName = resolver.getKey(propertyName);
791 }
792 }
793
794 if (resolver.isIndexed(propertyName) || resolver.isMapped(propertyName)) {
795 throw new IllegalArgumentException("Indexed or mapped properties are not supported on" + " objects of type Map: " + propertyName);
796 }
797
798 return bean.get(propertyName);
799 }
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820 public Class<?> getPropertyType(Object bean, String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
821 Objects.requireNonNull(bean, "bean");
822 Objects.requireNonNull(name, "name");
823
824 while (resolver.hasNested(name)) {
825 final String next = resolver.next(name);
826 final Object nestedBean = getProperty(bean, next);
827 if (nestedBean == null) {
828 throw new NestedNullException("Null property value for '" + next + "' on bean class '" + bean.getClass() + "'");
829 }
830 bean = nestedBean;
831 name = resolver.remove(name);
832 }
833
834
835 name = resolver.getProperty(name);
836
837
838 if (bean instanceof DynaBean) {
839 final DynaProperty descriptor = ((DynaBean) bean).getDynaClass().getDynaProperty(name);
840 if (descriptor == null) {
841 return null;
842 }
843 final Class<?> type = descriptor.getType();
844 if (type == null) {
845 return null;
846 }
847 if (type.isArray()) {
848 return type.getComponentType();
849 }
850 return type;
851 }
852
853 final PropertyDescriptor descriptor = getPropertyDescriptor(bean, name);
854 if (descriptor == null) {
855 return null;
856 }
857 if (descriptor instanceof IndexedPropertyDescriptor) {
858 return ((IndexedPropertyDescriptor) descriptor).getIndexedPropertyType();
859 }
860 if (descriptor instanceof MappedPropertyDescriptor) {
861 return ((MappedPropertyDescriptor) descriptor).getMappedPropertyType();
862 }
863 return descriptor.getPropertyType();
864 }
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886 public Method getReadMethod(final Class<?> clazz, final PropertyDescriptor descriptor) {
887 return MethodUtils.getAccessibleMethod(clazz, descriptor.getReadMethod());
888 }
889
890
891
892
893
894
895
896
897
898
899
900
901
902 public Method getReadMethod(final PropertyDescriptor descriptor) {
903 return MethodUtils.getAccessibleMethod(descriptor.getReadMethod());
904 }
905
906
907
908
909
910
911
912
913
914
915
916
917 public Resolver getResolver() {
918 return resolver;
919 }
920
921
922
923
924
925
926
927
928
929
930
931
932
933 public Object getSimpleProperty(final Object bean, final String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
934 Objects.requireNonNull(bean, "bean");
935 Objects.requireNonNull(name, "name");
936
937 if (resolver.hasNested(name)) {
938 throw new IllegalArgumentException("Nested property names are not allowed: Property '" + name + "' on bean class '" + bean.getClass() + "'");
939 }
940 if (resolver.isIndexed(name)) {
941 throw new IllegalArgumentException("Indexed property names are not allowed: Property '" + name + "' on bean class '" + bean.getClass() + "'");
942 }
943 if (resolver.isMapped(name)) {
944 throw new IllegalArgumentException("Mapped property names are not allowed: Property '" + name + "' on bean class '" + bean.getClass() + "'");
945 }
946
947
948 if (bean instanceof DynaBean) {
949 final DynaProperty descriptor = ((DynaBean) bean).getDynaClass().getDynaProperty(name);
950 if (descriptor == null) {
951 throw new NoSuchMethodException("Unknown property '" + name + "' on dynaclass '" + ((DynaBean) bean).getDynaClass() + "'");
952 }
953 return ((DynaBean) bean).get(name);
954 }
955
956
957 final PropertyDescriptor descriptor = getPropertyDescriptor(bean, name);
958 if (descriptor == null) {
959 throw new NoSuchMethodException("Unknown property '" + name + "' on class '" + bean.getClass() + "'");
960 }
961 final Method readMethod = getReadMethod(bean.getClass(), descriptor);
962 if (readMethod == null) {
963 throw new NoSuchMethodException("Property '" + name + "' has no getter method in class '" + bean.getClass() + "'");
964 }
965
966
967 return invokeMethod(readMethod, bean, BeanUtils.EMPTY_OBJECT_ARRAY);
968 }
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990 public Method getWriteMethod(final Class<?> clazz, final PropertyDescriptor descriptor) {
991 final BeanIntrospectionData data = getIntrospectionData(clazz);
992 return MethodUtils.getAccessibleMethod(clazz, data.getWriteMethod(clazz, descriptor));
993 }
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011 public Method getWriteMethod(final PropertyDescriptor descriptor) {
1012 return MethodUtils.getAccessibleMethod(descriptor.getWriteMethod());
1013 }
1014
1015
1016
1017
1018
1019
1020 private Object invokeMethod(final Method method, final Object bean, final Object... values) throws IllegalAccessException, InvocationTargetException {
1021 Objects.requireNonNull(bean, "bean");
1022 try {
1023 return method.invoke(bean, values);
1024 } catch (final NullPointerException | IllegalArgumentException cause) {
1025
1026
1027 final StringBuilder valueString = new StringBuilder();
1028 if (values != null) {
1029 for (int i = 0; i < values.length; i++) {
1030 if (i > 0) {
1031 valueString.append(", ");
1032 }
1033 if (values[i] == null) {
1034 valueString.append("<null>");
1035 } else {
1036 valueString.append(values[i].getClass().getName());
1037 }
1038 }
1039 }
1040 final StringBuilder expectedString = new StringBuilder();
1041 final Class<?>[] parTypes = method.getParameterTypes();
1042 if (parTypes != null) {
1043 for (int i = 0; i < parTypes.length; i++) {
1044 if (i > 0) {
1045 expectedString.append(", ");
1046 }
1047 expectedString.append(parTypes[i].getName());
1048 }
1049 }
1050 throw new IllegalArgumentException("Cannot invoke " + method.getDeclaringClass().getName() + "." + method.getName() + " on bean class '"
1051 + bean.getClass() + "' - " + cause.getMessage()
1052
1053 + " - had objects of type \"" + valueString + "\" but expected signature \"" + expectedString + "\"", cause);
1054 }
1055 }
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066 public boolean isReadable(Object bean, String name) {
1067
1068 Objects.requireNonNull(bean, "bean");
1069 Objects.requireNonNull(name, "name");
1070
1071 while (resolver.hasNested(name)) {
1072 final String next = resolver.next(name);
1073 Object nestedBean = null;
1074 try {
1075 nestedBean = getProperty(bean, next);
1076 } catch (final IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
1077 return false;
1078 }
1079 if (nestedBean == null) {
1080 throw new NestedNullException("Null property value for '" + next + "' on bean class '" + bean.getClass() + "'");
1081 }
1082 bean = nestedBean;
1083 name = resolver.remove(name);
1084 }
1085
1086
1087 name = resolver.getProperty(name);
1088
1089
1090
1091 if (bean instanceof WrapDynaBean) {
1092 bean = ((WrapDynaBean) bean).getInstance();
1093 }
1094
1095
1096 if (bean instanceof DynaBean) {
1097
1098 return ((DynaBean) bean).getDynaClass().getDynaProperty(name) != null;
1099 }
1100 try {
1101 final PropertyDescriptor desc = getPropertyDescriptor(bean, name);
1102 if (desc != null) {
1103 Method readMethod = getReadMethod(bean.getClass(), desc);
1104 if (readMethod == null) {
1105 if (desc instanceof IndexedPropertyDescriptor) {
1106 readMethod = ((IndexedPropertyDescriptor) desc).getIndexedReadMethod();
1107 } else if (desc instanceof MappedPropertyDescriptor) {
1108 readMethod = ((MappedPropertyDescriptor) desc).getMappedReadMethod();
1109 }
1110 readMethod = MethodUtils.getAccessibleMethod(bean.getClass(), readMethod);
1111 }
1112 return readMethod != null;
1113 }
1114 return false;
1115 } catch (final IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
1116 return false;
1117 }
1118 }
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129 public boolean isWriteable(Object bean, String name) {
1130
1131 Objects.requireNonNull(bean, "bean");
1132 Objects.requireNonNull(name, "name");
1133
1134 while (resolver.hasNested(name)) {
1135 final String next = resolver.next(name);
1136 Object nestedBean = null;
1137 try {
1138 nestedBean = getProperty(bean, next);
1139 } catch (final IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
1140 return false;
1141 }
1142 if (nestedBean == null) {
1143 throw new NestedNullException("Null property value for '" + next + "' on bean class '" + bean.getClass() + "'");
1144 }
1145 bean = nestedBean;
1146 name = resolver.remove(name);
1147 }
1148
1149
1150 name = resolver.getProperty(name);
1151
1152
1153
1154 if (bean instanceof WrapDynaBean) {
1155 bean = ((WrapDynaBean) bean).getInstance();
1156 }
1157
1158
1159 if (bean instanceof DynaBean) {
1160
1161 return ((DynaBean) bean).getDynaClass().getDynaProperty(name) != null;
1162 }
1163 try {
1164 final PropertyDescriptor desc = getPropertyDescriptor(bean, name);
1165 if (desc != null) {
1166 Method writeMethod = getWriteMethod(bean.getClass(), desc);
1167 if (writeMethod == null) {
1168 if (desc instanceof IndexedPropertyDescriptor) {
1169 writeMethod = ((IndexedPropertyDescriptor) desc).getIndexedWriteMethod();
1170 } else if (desc instanceof MappedPropertyDescriptor) {
1171 writeMethod = ((MappedPropertyDescriptor) desc).getMappedWriteMethod();
1172 }
1173 writeMethod = MethodUtils.getAccessibleMethod(bean.getClass(), writeMethod);
1174 }
1175 return writeMethod != null;
1176 }
1177 return false;
1178 } catch (final IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
1179 return false;
1180 }
1181 }
1182
1183
1184
1185
1186
1187
1188
1189
1190 public boolean removeBeanIntrospector(final BeanIntrospector introspector) {
1191 return introspectors.remove(introspector);
1192 }
1193
1194
1195
1196
1197
1198
1199
1200 public final void resetBeanIntrospectors() {
1201 introspectors.clear();
1202 introspectors.add(DefaultBeanIntrospector.INSTANCE);
1203 introspectors.add(SuppressPropertiesBeanIntrospector.SUPPRESS_CLASS);
1204 }
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220 public void setIndexedProperty(final Object bean, final String name, final int index, final Object value)
1221 throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
1222 Objects.requireNonNull(bean, "bean");
1223 if (name == null || name.isEmpty()) {
1224 if (bean.getClass().isArray()) {
1225 Array.set(bean, index, value);
1226 return;
1227 }
1228 if (bean instanceof List) {
1229 final List<Object> list = toObjectList(bean);
1230 list.set(index, value);
1231 return;
1232 }
1233 }
1234 Objects.requireNonNull(name, "name");
1235
1236 if (bean instanceof DynaBean) {
1237 final DynaProperty descriptor = ((DynaBean) bean).getDynaClass().getDynaProperty(name);
1238 if (descriptor == null) {
1239 throw new NoSuchMethodException("Unknown property '" + name + "' on bean class '" + bean.getClass() + "'");
1240 }
1241 ((DynaBean) bean).set(name, index, value);
1242 return;
1243 }
1244
1245
1246 final PropertyDescriptor descriptor = getPropertyDescriptor(bean, name);
1247 if (descriptor == null) {
1248 throw new NoSuchMethodException("Unknown property '" + name + "' on bean class '" + bean.getClass() + "'");
1249 }
1250
1251
1252 if (descriptor instanceof IndexedPropertyDescriptor) {
1253 Method writeMethod = ((IndexedPropertyDescriptor) descriptor).getIndexedWriteMethod();
1254 writeMethod = MethodUtils.getAccessibleMethod(bean.getClass(), writeMethod);
1255 if (writeMethod != null) {
1256 try {
1257 if (LOG.isTraceEnabled()) {
1258 final String valueClassName = value == null ? "<null>" : value.getClass().getName();
1259 LOG.trace("setSimpleProperty: Invoking method " + writeMethod + " with index=" + index + ", value=" + value + " (class "
1260 + valueClassName + ")");
1261 }
1262 invokeMethod(writeMethod, bean, Integer.valueOf(index), value);
1263 } catch (final InvocationTargetException e) {
1264 if (e.getTargetException() instanceof IndexOutOfBoundsException) {
1265 throw (IndexOutOfBoundsException) e.getTargetException();
1266 }
1267 throw e;
1268 }
1269 return;
1270 }
1271 }
1272
1273
1274 final Method readMethod = getReadMethod(bean.getClass(), descriptor);
1275 if (readMethod == null) {
1276 throw new NoSuchMethodException("Property '" + name + "' has no getter method on bean class '" + bean.getClass() + "'");
1277 }
1278
1279
1280 final Object array = invokeMethod(readMethod, bean, BeanUtils.EMPTY_OBJECT_ARRAY);
1281 if (!array.getClass().isArray()) {
1282 if (!(array instanceof List)) {
1283 throw new IllegalArgumentException("Property '" + name + "' is not indexed on bean class '" + bean.getClass() + "'");
1284 }
1285
1286 final List<Object> list = toObjectList(array);
1287 list.set(index, value);
1288 } else {
1289
1290 Array.set(array, index, value);
1291 }
1292 }
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308 public void setIndexedProperty(final Object bean, String name, final Object value)
1309 throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
1310 Objects.requireNonNull(bean, "bean");
1311 Objects.requireNonNull(name, "name");
1312
1313 int index = -1;
1314 try {
1315 index = resolver.getIndex(name);
1316 } catch (final IllegalArgumentException e) {
1317 throw new IllegalArgumentException("Invalid indexed property '" + name + "' on bean class '" + bean.getClass() + "'");
1318 }
1319 if (index < 0) {
1320 throw new IllegalArgumentException("Invalid indexed property '" + name + "' on bean class '" + bean.getClass() + "'");
1321 }
1322
1323
1324 name = resolver.getProperty(name);
1325
1326
1327 setIndexedProperty(bean, name, index, value);
1328 }
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341 public void setMappedProperty(final Object bean, String name, final Object value)
1342 throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
1343 Objects.requireNonNull(bean, "bean");
1344 Objects.requireNonNull(name, "name");
1345
1346
1347 String key = null;
1348 try {
1349 key = resolver.getKey(name);
1350 } catch (final IllegalArgumentException e) {
1351 throw new IllegalArgumentException("Invalid mapped property '" + name + "' on bean class '" + bean.getClass() + "'");
1352 }
1353 if (key == null) {
1354 throw new IllegalArgumentException("Invalid mapped property '" + name + "' on bean class '" + bean.getClass() + "'");
1355 }
1356
1357
1358 name = resolver.getProperty(name);
1359
1360
1361 setMappedProperty(bean, name, key, value);
1362 }
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375 public void setMappedProperty(final Object bean, final String name, final String key, final Object value)
1376 throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
1377 Objects.requireNonNull(bean, "bean");
1378 Objects.requireNonNull(name, "name");
1379 Objects.requireNonNull(key, "key");
1380
1381 if (bean instanceof DynaBean) {
1382 final DynaProperty descriptor = ((DynaBean) bean).getDynaClass().getDynaProperty(name);
1383 if (descriptor == null) {
1384 throw new NoSuchMethodException("Unknown property '" + name + "' on bean class '" + bean.getClass() + "'");
1385 }
1386 ((DynaBean) bean).set(name, key, value);
1387 return;
1388 }
1389
1390
1391 final PropertyDescriptor descriptor = getPropertyDescriptor(bean, name);
1392 if (descriptor == null) {
1393 throw new NoSuchMethodException("Unknown property '" + name + "' on bean class '" + bean.getClass() + "'");
1394 }
1395
1396 if (descriptor instanceof MappedPropertyDescriptor) {
1397
1398 Method mappedWriteMethod = ((MappedPropertyDescriptor) descriptor).getMappedWriteMethod();
1399 mappedWriteMethod = MethodUtils.getAccessibleMethod(bean.getClass(), mappedWriteMethod);
1400 if (mappedWriteMethod == null) {
1401 throw new NoSuchMethodException("Property '" + name + "' has no mapped setter method" + "on bean class '" + bean.getClass() + "'");
1402 }
1403 if (LOG.isTraceEnabled()) {
1404 final String valueClassName = value == null ? "<null>" : value.getClass().getName();
1405 LOG.trace("setSimpleProperty: Invoking method " + mappedWriteMethod + " with key=" + key + ", value=" + value + " (class " + valueClassName
1406 + ")");
1407 }
1408 invokeMethod(mappedWriteMethod, bean, key, value);
1409 } else {
1410
1411 final Method readMethod = getReadMethod(bean.getClass(), descriptor);
1412 if (readMethod == null) {
1413 throw new NoSuchMethodException("Property '" + name + "' has no mapped getter method on bean class '" + bean.getClass() + "'");
1414 }
1415 final Object invokeResult = invokeMethod(readMethod, bean, BeanUtils.EMPTY_OBJECT_ARRAY);
1416
1417 if (invokeResult instanceof Map) {
1418 toPropertyMap(invokeResult).put(key, value);
1419 }
1420 }
1421 }
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443 public void setNestedProperty(Object bean, String name, final Object value)
1444 throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
1445 Objects.requireNonNull(bean, "bean");
1446 Objects.requireNonNull(name, "name");
1447
1448 while (resolver.hasNested(name)) {
1449 final String next = resolver.next(name);
1450 Object nestedBean = null;
1451 if (bean instanceof Map) {
1452 nestedBean = getPropertyOfMapBean((Map<?, ?>) bean, next);
1453 } else if (resolver.isMapped(next)) {
1454 nestedBean = getMappedProperty(bean, next);
1455 } else if (resolver.isIndexed(next)) {
1456 nestedBean = getIndexedProperty(bean, next);
1457 } else {
1458 nestedBean = getSimpleProperty(bean, next);
1459 }
1460 if (nestedBean == null) {
1461 throw new NestedNullException("Null property value for '" + name + "' on bean class '" + bean.getClass() + "'");
1462 }
1463 bean = nestedBean;
1464 name = resolver.remove(name);
1465 }
1466
1467 if (bean instanceof Map) {
1468 setPropertyOfMapBean(toPropertyMap(bean), name, value);
1469 } else if (resolver.isMapped(name)) {
1470 setMappedProperty(bean, name, value);
1471 } else if (resolver.isIndexed(name)) {
1472 setIndexedProperty(bean, name, value);
1473 } else {
1474 setSimpleProperty(bean, name, value);
1475 }
1476 }
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489 public void setProperty(final Object bean, final String name, final Object value)
1490 throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
1491 setNestedProperty(bean, name, value);
1492 }
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529 protected void setPropertyOfMapBean(final Map<String, Object> bean, String propertyName, final Object value)
1530 throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
1531 if (resolver.isMapped(propertyName)) {
1532 final String name = resolver.getProperty(propertyName);
1533 if (name == null || name.isEmpty()) {
1534 propertyName = resolver.getKey(propertyName);
1535 }
1536 }
1537
1538 if (resolver.isIndexed(propertyName) || resolver.isMapped(propertyName)) {
1539 throw new IllegalArgumentException("Indexed or mapped properties are not supported on" + " objects of type Map: " + propertyName);
1540 }
1541
1542 bean.put(propertyName, value);
1543 }
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556 public void setResolver(final Resolver resolver) {
1557 if (resolver == null) {
1558 this.resolver = new DefaultResolver();
1559 } else {
1560 this.resolver = resolver;
1561 }
1562 }
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576 public void setSimpleProperty(final Object bean, final String name, final Object value)
1577 throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
1578 Objects.requireNonNull(bean, "bean");
1579 Objects.requireNonNull(name, "name");
1580 final Class<?> beanClass = bean.getClass();
1581
1582 if (resolver.hasNested(name)) {
1583 throw new IllegalArgumentException("Nested property names are not allowed: Property '" + name + "' on bean class '" + beanClass + "'");
1584 }
1585 if (resolver.isIndexed(name)) {
1586 throw new IllegalArgumentException("Indexed property names are not allowed: Property '" + name + "' on bean class '" + beanClass + "'");
1587 }
1588 if (resolver.isMapped(name)) {
1589 throw new IllegalArgumentException("Mapped property names are not allowed: Property '" + name + "' on bean class '" + beanClass + "'");
1590 }
1591
1592
1593 if (bean instanceof DynaBean) {
1594 final DynaProperty descriptor = ((DynaBean) bean).getDynaClass().getDynaProperty(name);
1595 if (descriptor == null) {
1596 throw new NoSuchMethodException("Unknown property '" + name + "' on dynaclass '" + ((DynaBean) bean).getDynaClass() + "'");
1597 }
1598 ((DynaBean) bean).set(name, value);
1599 return;
1600 }
1601
1602
1603 final PropertyDescriptor descriptor = getPropertyDescriptor(bean, name);
1604 if (descriptor == null) {
1605 throw new NoSuchMethodException("Unknown property '" + name + "' on class '" + beanClass + "'");
1606 }
1607 final Method writeMethod = getWriteMethod(beanClass, descriptor);
1608 if (writeMethod == null) {
1609 throw new NoSuchMethodException("Property '" + name + "' has no setter method in class '" + beanClass + "'");
1610 }
1611
1612
1613 if (LOG.isTraceEnabled()) {
1614 final String valueClassName = value == null ? "<null>" : value.getClass().getName();
1615 LOG.trace("setSimpleProperty: Invoking method " + writeMethod + " with value " + value + " (class " + valueClassName + ")");
1616 }
1617 invokeMethod(writeMethod, bean, value);
1618 }
1619 }