1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.springframework.beans;
18
19 import java.beans.PropertyChangeEvent;
20 import java.beans.PropertyDescriptor;
21 import java.lang.reflect.Array;
22 import java.lang.reflect.InvocationTargetException;
23 import java.lang.reflect.Method;
24 import java.lang.reflect.Modifier;
25 import java.security.AccessControlContext;
26 import java.security.AccessController;
27 import java.security.PrivilegedAction;
28 import java.security.PrivilegedActionException;
29 import java.security.PrivilegedExceptionAction;
30 import java.util.ArrayList;
31 import java.util.Collection;
32 import java.util.HashMap;
33 import java.util.Iterator;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.Optional;
37 import java.util.Set;
38
39 import org.apache.commons.logging.Log;
40 import org.apache.commons.logging.LogFactory;
41
42 import org.springframework.core.CollectionFactory;
43 import org.springframework.core.GenericCollectionTypeResolver;
44 import org.springframework.core.convert.ConversionException;
45 import org.springframework.core.convert.ConverterNotFoundException;
46 import org.springframework.core.convert.Property;
47 import org.springframework.core.convert.TypeDescriptor;
48 import org.springframework.lang.UsesJava8;
49 import org.springframework.util.Assert;
50 import org.springframework.util.ClassUtils;
51 import org.springframework.util.ObjectUtils;
52 import org.springframework.util.StringUtils;
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91 public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWrapper {
92
93
94
95
96 private static final Log logger = LogFactory.getLog(BeanWrapperImpl.class);
97
98 private static Class<?> javaUtilOptionalClass = null;
99
100 static {
101 try {
102 javaUtilOptionalClass =
103 ClassUtils.forName("java.util.Optional", BeanWrapperImpl.class.getClassLoader());
104 }
105 catch (ClassNotFoundException ex) {
106
107 }
108 }
109
110
111
112 private Object object;
113
114 private String nestedPath = "";
115
116 private Object rootObject;
117
118
119
120
121 private AccessControlContext acc;
122
123
124
125
126
127 private CachedIntrospectionResults cachedIntrospectionResults;
128
129
130
131
132 private Map<String, BeanWrapperImpl> nestedBeanWrappers;
133
134 private int autoGrowCollectionLimit = Integer.MAX_VALUE;
135
136
137
138
139
140
141
142 public BeanWrapperImpl() {
143 this(true);
144 }
145
146
147
148
149
150
151
152 public BeanWrapperImpl(boolean registerDefaultEditors) {
153 if (registerDefaultEditors) {
154 registerDefaultEditors();
155 }
156 this.typeConverterDelegate = new TypeConverterDelegate(this);
157 }
158
159
160
161
162
163 public BeanWrapperImpl(Object object) {
164 registerDefaultEditors();
165 setWrappedInstance(object);
166 }
167
168
169
170
171
172 public BeanWrapperImpl(Class<?> clazz) {
173 registerDefaultEditors();
174 setWrappedInstance(BeanUtils.instantiateClass(clazz));
175 }
176
177
178
179
180
181
182
183
184 public BeanWrapperImpl(Object object, String nestedPath, Object rootObject) {
185 registerDefaultEditors();
186 setWrappedInstance(object, nestedPath, rootObject);
187 }
188
189
190
191
192
193
194
195
196 private BeanWrapperImpl(Object object, String nestedPath, BeanWrapperImpl superBw) {
197 setWrappedInstance(object, nestedPath, superBw.getWrappedInstance());
198 setExtractOldValueForEditor(superBw.isExtractOldValueForEditor());
199 setAutoGrowNestedPaths(superBw.isAutoGrowNestedPaths());
200 setAutoGrowCollectionLimit(superBw.getAutoGrowCollectionLimit());
201 setConversionService(superBw.getConversionService());
202 setSecurityContext(superBw.acc);
203 }
204
205
206
207
208
209
210
211
212
213
214
215 public void setWrappedInstance(Object object) {
216 setWrappedInstance(object, "", null);
217 }
218
219
220
221
222
223
224
225
226 public void setWrappedInstance(Object object, String nestedPath, Object rootObject) {
227 Assert.notNull(object, "Bean object must not be null");
228 if (object.getClass().equals(javaUtilOptionalClass)) {
229 this.object = OptionalUnwrapper.unwrap(object);
230 }
231 else {
232 this.object = object;
233 }
234 this.nestedPath = (nestedPath != null ? nestedPath : "");
235 this.rootObject = (!"".equals(this.nestedPath) ? rootObject : this.object);
236 this.nestedBeanWrappers = null;
237 this.typeConverterDelegate = new TypeConverterDelegate(this, this.object);
238 setIntrospectionClass(this.object.getClass());
239 }
240
241 @Override
242 public final Object getWrappedInstance() {
243 return this.object;
244 }
245
246 @Override
247 public final Class<?> getWrappedClass() {
248 return (this.object != null ? this.object.getClass() : null);
249 }
250
251
252
253
254 public final String getNestedPath() {
255 return this.nestedPath;
256 }
257
258
259
260
261
262 public final Object getRootInstance() {
263 return this.rootObject;
264 }
265
266
267
268
269
270 public final Class<?> getRootClass() {
271 return (this.rootObject != null ? this.rootObject.getClass() : null);
272 }
273
274
275
276
277
278
279
280 @Override
281 public void setAutoGrowCollectionLimit(int autoGrowCollectionLimit) {
282 this.autoGrowCollectionLimit = autoGrowCollectionLimit;
283 }
284
285
286
287
288 @Override
289 public int getAutoGrowCollectionLimit() {
290 return this.autoGrowCollectionLimit;
291 }
292
293
294
295
296
297 public void setSecurityContext(AccessControlContext acc) {
298 this.acc = acc;
299 }
300
301
302
303
304
305 public AccessControlContext getSecurityContext() {
306 return this.acc;
307 }
308
309
310
311
312
313
314 protected void setIntrospectionClass(Class<?> clazz) {
315 if (this.cachedIntrospectionResults != null &&
316 !clazz.equals(this.cachedIntrospectionResults.getBeanClass())) {
317 this.cachedIntrospectionResults = null;
318 }
319 }
320
321
322
323
324
325 private CachedIntrospectionResults getCachedIntrospectionResults() {
326 Assert.state(this.object != null, "BeanWrapper does not hold a bean instance");
327 if (this.cachedIntrospectionResults == null) {
328 this.cachedIntrospectionResults = CachedIntrospectionResults.forClass(getWrappedClass());
329 }
330 return this.cachedIntrospectionResults;
331 }
332
333
334 @Override
335 public PropertyDescriptor[] getPropertyDescriptors() {
336 return getCachedIntrospectionResults().getPropertyDescriptors();
337 }
338
339 @Override
340 public PropertyDescriptor getPropertyDescriptor(String propertyName) throws BeansException {
341 PropertyDescriptor pd = getPropertyDescriptorInternal(propertyName);
342 if (pd == null) {
343 throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
344 "No property '" + propertyName + "' found");
345 }
346 return pd;
347 }
348
349
350
351
352
353
354
355
356
357 protected PropertyDescriptor getPropertyDescriptorInternal(String propertyName) throws BeansException {
358 Assert.notNull(propertyName, "Property name must not be null");
359 BeanWrapperImpl nestedBw = getBeanWrapperForPropertyPath(propertyName);
360 return nestedBw.getCachedIntrospectionResults().getPropertyDescriptor(getFinalPath(nestedBw, propertyName));
361 }
362
363 @Override
364 public Class<?> getPropertyType(String propertyName) throws BeansException {
365 try {
366 PropertyDescriptor pd = getPropertyDescriptorInternal(propertyName);
367 if (pd != null) {
368 return pd.getPropertyType();
369 }
370 else {
371
372 Object value = getPropertyValue(propertyName);
373 if (value != null) {
374 return value.getClass();
375 }
376
377
378 Class<?> editorType = guessPropertyTypeFromEditors(propertyName);
379 if (editorType != null) {
380 return editorType;
381 }
382 }
383 }
384 catch (InvalidPropertyException ex) {
385
386 }
387 return null;
388 }
389
390 @Override
391 public TypeDescriptor getPropertyTypeDescriptor(String propertyName) throws BeansException {
392 try {
393 BeanWrapperImpl nestedBw = getBeanWrapperForPropertyPath(propertyName);
394 String finalPath = getFinalPath(nestedBw, propertyName);
395 PropertyTokenHolder tokens = getPropertyNameTokens(finalPath);
396 PropertyDescriptor pd = nestedBw.getCachedIntrospectionResults().getPropertyDescriptor(tokens.actualName);
397 if (pd != null) {
398 if (tokens.keys != null) {
399 if (pd.getReadMethod() != null || pd.getWriteMethod() != null) {
400 return TypeDescriptor.nested(property(pd), tokens.keys.length);
401 }
402 }
403 else {
404 if (pd.getReadMethod() != null || pd.getWriteMethod() != null) {
405 return new TypeDescriptor(property(pd));
406 }
407 }
408 }
409 }
410 catch (InvalidPropertyException ex) {
411
412 }
413 return null;
414 }
415
416 @Override
417 public boolean isReadableProperty(String propertyName) {
418 try {
419 PropertyDescriptor pd = getPropertyDescriptorInternal(propertyName);
420 if (pd != null) {
421 if (pd.getReadMethod() != null) {
422 return true;
423 }
424 }
425 else {
426
427 getPropertyValue(propertyName);
428 return true;
429 }
430 }
431 catch (InvalidPropertyException ex) {
432
433 }
434 return false;
435 }
436
437 @Override
438 public boolean isWritableProperty(String propertyName) {
439 try {
440 PropertyDescriptor pd = getPropertyDescriptorInternal(propertyName);
441 if (pd != null) {
442 if (pd.getWriteMethod() != null) {
443 return true;
444 }
445 }
446 else {
447
448 getPropertyValue(propertyName);
449 return true;
450 }
451 }
452 catch (InvalidPropertyException ex) {
453
454 }
455 return false;
456 }
457
458 private Object convertIfNecessary(String propertyName, Object oldValue, Object newValue, Class<?> requiredType,
459 TypeDescriptor td) throws TypeMismatchException {
460 try {
461 return this.typeConverterDelegate.convertIfNecessary(propertyName, oldValue, newValue, requiredType, td);
462 }
463 catch (ConverterNotFoundException ex) {
464 PropertyChangeEvent pce =
465 new PropertyChangeEvent(this.rootObject, this.nestedPath + propertyName, oldValue, newValue);
466 throw new ConversionNotSupportedException(pce, td.getType(), ex);
467 }
468 catch (ConversionException ex) {
469 PropertyChangeEvent pce =
470 new PropertyChangeEvent(this.rootObject, this.nestedPath + propertyName, oldValue, newValue);
471 throw new TypeMismatchException(pce, requiredType, ex);
472 }
473 catch (IllegalStateException ex) {
474 PropertyChangeEvent pce =
475 new PropertyChangeEvent(this.rootObject, this.nestedPath + propertyName, oldValue, newValue);
476 throw new ConversionNotSupportedException(pce, requiredType, ex);
477 }
478 catch (IllegalArgumentException ex) {
479 PropertyChangeEvent pce =
480 new PropertyChangeEvent(this.rootObject, this.nestedPath + propertyName, oldValue, newValue);
481 throw new TypeMismatchException(pce, requiredType, ex);
482 }
483 }
484
485
486
487
488
489
490
491
492
493
494
495 public Object convertForProperty(Object value, String propertyName) throws TypeMismatchException {
496 CachedIntrospectionResults cachedIntrospectionResults = getCachedIntrospectionResults();
497 PropertyDescriptor pd = cachedIntrospectionResults.getPropertyDescriptor(propertyName);
498 if (pd == null) {
499 throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
500 "No property '" + propertyName + "' found");
501 }
502 TypeDescriptor td = cachedIntrospectionResults.getTypeDescriptor(pd);
503 if (td == null) {
504 td = cachedIntrospectionResults.addTypeDescriptor(pd, new TypeDescriptor(property(pd)));
505 }
506 return convertForProperty(propertyName, null, value, td);
507 }
508
509 private Object convertForProperty(String propertyName, Object oldValue, Object newValue, TypeDescriptor td)
510 throws TypeMismatchException {
511
512 return convertIfNecessary(propertyName, oldValue, newValue, td.getType(), td);
513 }
514
515 private Property property(PropertyDescriptor pd) {
516 GenericTypeAwarePropertyDescriptor typeAware = (GenericTypeAwarePropertyDescriptor) pd;
517 return new Property(typeAware.getBeanClass(), typeAware.getReadMethod(), typeAware.getWriteMethod(), typeAware.getName());
518 }
519
520
521
522
523
524
525
526
527
528
529
530
531 private String getFinalPath(BeanWrapper bw, String nestedPath) {
532 if (bw == this) {
533 return nestedPath;
534 }
535 return nestedPath.substring(PropertyAccessorUtils.getLastNestedPropertySeparatorIndex(nestedPath) + 1);
536 }
537
538
539
540
541
542
543 protected BeanWrapperImpl getBeanWrapperForPropertyPath(String propertyPath) {
544 int pos = PropertyAccessorUtils.getFirstNestedPropertySeparatorIndex(propertyPath);
545
546 if (pos > -1) {
547 String nestedProperty = propertyPath.substring(0, pos);
548 String nestedPath = propertyPath.substring(pos + 1);
549 BeanWrapperImpl nestedBw = getNestedBeanWrapper(nestedProperty);
550 return nestedBw.getBeanWrapperForPropertyPath(nestedPath);
551 }
552 else {
553 return this;
554 }
555 }
556
557
558
559
560
561
562
563
564
565 private BeanWrapperImpl getNestedBeanWrapper(String nestedProperty) {
566 if (this.nestedBeanWrappers == null) {
567 this.nestedBeanWrappers = new HashMap<String, BeanWrapperImpl>();
568 }
569
570 PropertyTokenHolder tokens = getPropertyNameTokens(nestedProperty);
571 String canonicalName = tokens.canonicalName;
572 Object value = getPropertyValue(tokens);
573 if (value == null || (value.getClass().equals(javaUtilOptionalClass) && OptionalUnwrapper.isEmpty(value))) {
574 if (isAutoGrowNestedPaths()) {
575 value = setDefaultValue(tokens);
576 }
577 else {
578 throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + canonicalName);
579 }
580 }
581
582
583 BeanWrapperImpl nestedBw = this.nestedBeanWrappers.get(canonicalName);
584 if (nestedBw == null || nestedBw.getWrappedInstance() !=
585 (value.getClass().equals(javaUtilOptionalClass) ? OptionalUnwrapper.unwrap(value) : value)) {
586 if (logger.isTraceEnabled()) {
587 logger.trace("Creating new nested BeanWrapper for property '" + canonicalName + "'");
588 }
589 nestedBw = newNestedBeanWrapper(value, this.nestedPath + canonicalName + NESTED_PROPERTY_SEPARATOR);
590
591 copyDefaultEditorsTo(nestedBw);
592 copyCustomEditorsTo(nestedBw, canonicalName);
593 this.nestedBeanWrappers.put(canonicalName, nestedBw);
594 }
595 else {
596 if (logger.isTraceEnabled()) {
597 logger.trace("Using cached nested BeanWrapper for property '" + canonicalName + "'");
598 }
599 }
600 return nestedBw;
601 }
602
603 private Object setDefaultValue(String propertyName) {
604 PropertyTokenHolder tokens = new PropertyTokenHolder();
605 tokens.actualName = propertyName;
606 tokens.canonicalName = propertyName;
607 return setDefaultValue(tokens);
608 }
609
610 private Object setDefaultValue(PropertyTokenHolder tokens) {
611 PropertyValue pv = createDefaultPropertyValue(tokens);
612 setPropertyValue(tokens, pv);
613 return getPropertyValue(tokens);
614 }
615
616 private PropertyValue createDefaultPropertyValue(PropertyTokenHolder tokens) {
617 TypeDescriptor desc = getPropertyTypeDescriptor(tokens.canonicalName);
618 Class<?> type = desc.getType();
619 if (type == null) {
620 throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + tokens.canonicalName,
621 "Could not determine property type for auto-growing a default value");
622 }
623 Object defaultValue = newValue(type, desc, tokens.canonicalName);
624 return new PropertyValue(tokens.canonicalName, defaultValue);
625 }
626
627 private Object newValue(Class<?> type, TypeDescriptor desc, String name) {
628 try {
629 if (type.isArray()) {
630 Class<?> componentType = type.getComponentType();
631
632 if (componentType.isArray()) {
633 Object array = Array.newInstance(componentType, 1);
634 Array.set(array, 0, Array.newInstance(componentType.getComponentType(), 0));
635 return array;
636 }
637 else {
638 return Array.newInstance(componentType, 0);
639 }
640 }
641 else if (Collection.class.isAssignableFrom(type)) {
642 TypeDescriptor elementDesc = (desc != null ? desc.getElementTypeDescriptor() : null);
643 return CollectionFactory.createCollection(type, (elementDesc != null ? elementDesc.getType() : null), 16);
644 }
645 else if (Map.class.isAssignableFrom(type)) {
646 TypeDescriptor keyDesc = (desc != null ? desc.getMapKeyTypeDescriptor() : null);
647 return CollectionFactory.createMap(type, (keyDesc != null ? keyDesc.getType() : null), 16);
648 }
649 else {
650 return type.newInstance();
651 }
652 }
653 catch (Exception ex) {
654
655
656 throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + name,
657 "Could not instantiate property type [" + type.getName() + "] to auto-grow nested property path: " + ex);
658 }
659 }
660
661
662
663
664
665
666
667
668
669 protected BeanWrapperImpl newNestedBeanWrapper(Object object, String nestedPath) {
670 return new BeanWrapperImpl(object, nestedPath, this);
671 }
672
673
674
675
676
677
678 private PropertyTokenHolder getPropertyNameTokens(String propertyName) {
679 PropertyTokenHolder tokens = new PropertyTokenHolder();
680 String actualName = null;
681 List<String> keys = new ArrayList<String>(2);
682 int searchIndex = 0;
683 while (searchIndex != -1) {
684 int keyStart = propertyName.indexOf(PROPERTY_KEY_PREFIX, searchIndex);
685 searchIndex = -1;
686 if (keyStart != -1) {
687 int keyEnd = propertyName.indexOf(PROPERTY_KEY_SUFFIX, keyStart + PROPERTY_KEY_PREFIX.length());
688 if (keyEnd != -1) {
689 if (actualName == null) {
690 actualName = propertyName.substring(0, keyStart);
691 }
692 String key = propertyName.substring(keyStart + PROPERTY_KEY_PREFIX.length(), keyEnd);
693 if ((key.startsWith("'") && key.endsWith("'")) || (key.startsWith("\"") && key.endsWith("\""))) {
694 key = key.substring(1, key.length() - 1);
695 }
696 keys.add(key);
697 searchIndex = keyEnd + PROPERTY_KEY_SUFFIX.length();
698 }
699 }
700 }
701 tokens.actualName = (actualName != null ? actualName : propertyName);
702 tokens.canonicalName = tokens.actualName;
703 if (!keys.isEmpty()) {
704 tokens.canonicalName +=
705 PROPERTY_KEY_PREFIX +
706 StringUtils.collectionToDelimitedString(keys, PROPERTY_KEY_SUFFIX + PROPERTY_KEY_PREFIX) +
707 PROPERTY_KEY_SUFFIX;
708 tokens.keys = StringUtils.toStringArray(keys);
709 }
710 return tokens;
711 }
712
713
714
715
716
717
718 @Override
719 public Object getPropertyValue(String propertyName) throws BeansException {
720 BeanWrapperImpl nestedBw = getBeanWrapperForPropertyPath(propertyName);
721 PropertyTokenHolder tokens = getPropertyNameTokens(getFinalPath(nestedBw, propertyName));
722 return nestedBw.getPropertyValue(tokens);
723 }
724
725 @SuppressWarnings("unchecked")
726 private Object getPropertyValue(PropertyTokenHolder tokens) throws BeansException {
727 String propertyName = tokens.canonicalName;
728 String actualName = tokens.actualName;
729 PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(actualName);
730 if (pd == null || pd.getReadMethod() == null) {
731 throw new NotReadablePropertyException(getRootClass(), this.nestedPath + propertyName);
732 }
733 final Method readMethod = pd.getReadMethod();
734 try {
735 if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers()) && !readMethod.isAccessible()) {
736 if (System.getSecurityManager() != null) {
737 AccessController.doPrivileged(new PrivilegedAction<Object>() {
738 @Override
739 public Object run() {
740 readMethod.setAccessible(true);
741 return null;
742 }
743 });
744 }
745 else {
746 readMethod.setAccessible(true);
747 }
748 }
749
750 Object value;
751 if (System.getSecurityManager() != null) {
752 try {
753 value = AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
754 @Override
755 public Object run() throws Exception {
756 return readMethod.invoke(object, (Object[]) null);
757 }
758 }, acc);
759 }
760 catch (PrivilegedActionException pae) {
761 throw pae.getException();
762 }
763 }
764 else {
765 value = readMethod.invoke(object, (Object[]) null);
766 }
767
768 if (tokens.keys != null) {
769 if (value == null) {
770 if (isAutoGrowNestedPaths()) {
771 value = setDefaultValue(tokens.actualName);
772 }
773 else {
774 throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + propertyName,
775 "Cannot access indexed value of property referenced in indexed " +
776 "property path '" + propertyName + "': returned null");
777 }
778 }
779 String indexedPropertyName = tokens.actualName;
780
781 for (int i = 0; i < tokens.keys.length; i++) {
782 String key = tokens.keys[i];
783 if (value == null) {
784 throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + propertyName,
785 "Cannot access indexed value of property referenced in indexed " +
786 "property path '" + propertyName + "': returned null");
787 }
788 else if (value.getClass().isArray()) {
789 int index = Integer.parseInt(key);
790 value = growArrayIfNecessary(value, index, indexedPropertyName);
791 value = Array.get(value, index);
792 }
793 else if (value instanceof List) {
794 int index = Integer.parseInt(key);
795 List<Object> list = (List<Object>) value;
796 growCollectionIfNecessary(list, index, indexedPropertyName, pd, i + 1);
797 value = list.get(index);
798 }
799 else if (value instanceof Set) {
800
801 Set<Object> set = (Set<Object>) value;
802 int index = Integer.parseInt(key);
803 if (index < 0 || index >= set.size()) {
804 throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
805 "Cannot get element with index " + index + " from Set of size " +
806 set.size() + ", accessed using property path '" + propertyName + "'");
807 }
808 Iterator<Object> it = set.iterator();
809 for (int j = 0; it.hasNext(); j++) {
810 Object elem = it.next();
811 if (j == index) {
812 value = elem;
813 break;
814 }
815 }
816 }
817 else if (value instanceof Map) {
818 Map<Object, Object> map = (Map<Object, Object>) value;
819 Class<?> mapKeyType = GenericCollectionTypeResolver.getMapKeyReturnType(pd.getReadMethod(), i + 1);
820
821
822 TypeDescriptor typeDescriptor = TypeDescriptor.valueOf(mapKeyType);
823 Object convertedMapKey = convertIfNecessary(null, null, key, mapKeyType, typeDescriptor);
824 value = map.get(convertedMapKey);
825 }
826 else {
827 throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
828 "Property referenced in indexed property path '" + propertyName +
829 "' is neither an array nor a List nor a Set nor a Map; returned value was [" + value + "]");
830 }
831 indexedPropertyName += PROPERTY_KEY_PREFIX + key + PROPERTY_KEY_SUFFIX;
832 }
833 }
834 return value;
835 }
836 catch (IndexOutOfBoundsException ex) {
837 throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
838 "Index of out of bounds in property path '" + propertyName + "'", ex);
839 }
840 catch (NumberFormatException ex) {
841 throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
842 "Invalid index in property path '" + propertyName + "'", ex);
843 }
844 catch (TypeMismatchException ex) {
845 throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
846 "Invalid index in property path '" + propertyName + "'", ex);
847 }
848 catch (InvocationTargetException ex) {
849 throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
850 "Getter for property '" + actualName + "' threw exception", ex);
851 }
852 catch (Exception ex) {
853 throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
854 "Illegal attempt to get property '" + actualName + "' threw exception", ex);
855 }
856 }
857
858 private Object growArrayIfNecessary(Object array, int index, String name) {
859 if (!isAutoGrowNestedPaths()) {
860 return array;
861 }
862 int length = Array.getLength(array);
863 if (index >= length && index < this.autoGrowCollectionLimit) {
864 Class<?> componentType = array.getClass().getComponentType();
865 Object newArray = Array.newInstance(componentType, index + 1);
866 System.arraycopy(array, 0, newArray, 0, length);
867 for (int i = length; i < Array.getLength(newArray); i++) {
868 Array.set(newArray, i, newValue(componentType, null, name));
869 }
870
871 setPropertyValue(name, newArray);
872 return getPropertyValue(name);
873 }
874 else {
875 return array;
876 }
877 }
878
879 private void growCollectionIfNecessary(Collection<Object> collection, int index, String name,
880 PropertyDescriptor pd, int nestingLevel) {
881
882 if (!isAutoGrowNestedPaths()) {
883 return;
884 }
885 int size = collection.size();
886 if (index >= size && index < this.autoGrowCollectionLimit) {
887 Class<?> elementType = GenericCollectionTypeResolver.getCollectionReturnType(pd.getReadMethod(), nestingLevel);
888 if (elementType != null) {
889 for (int i = collection.size(); i < index + 1; i++) {
890 collection.add(newValue(elementType, null, name));
891 }
892 }
893 }
894 }
895
896 @Override
897 public void setPropertyValue(String propertyName, Object value) throws BeansException {
898 BeanWrapperImpl nestedBw;
899 try {
900 nestedBw = getBeanWrapperForPropertyPath(propertyName);
901 }
902 catch (NotReadablePropertyException ex) {
903 throw new NotWritablePropertyException(getRootClass(), this.nestedPath + propertyName,
904 "Nested property in path '" + propertyName + "' does not exist", ex);
905 }
906 PropertyTokenHolder tokens = getPropertyNameTokens(getFinalPath(nestedBw, propertyName));
907 nestedBw.setPropertyValue(tokens, new PropertyValue(propertyName, value));
908 }
909
910 @Override
911 public void setPropertyValue(PropertyValue pv) throws BeansException {
912 PropertyTokenHolder tokens = (PropertyTokenHolder) pv.resolvedTokens;
913 if (tokens == null) {
914 String propertyName = pv.getName();
915 BeanWrapperImpl nestedBw;
916 try {
917 nestedBw = getBeanWrapperForPropertyPath(propertyName);
918 }
919 catch (NotReadablePropertyException ex) {
920 throw new NotWritablePropertyException(getRootClass(), this.nestedPath + propertyName,
921 "Nested property in path '" + propertyName + "' does not exist", ex);
922 }
923 tokens = getPropertyNameTokens(getFinalPath(nestedBw, propertyName));
924 if (nestedBw == this) {
925 pv.getOriginalPropertyValue().resolvedTokens = tokens;
926 }
927 nestedBw.setPropertyValue(tokens, pv);
928 }
929 else {
930 setPropertyValue(tokens, pv);
931 }
932 }
933
934 @SuppressWarnings("unchecked")
935 private void setPropertyValue(PropertyTokenHolder tokens, PropertyValue pv) throws BeansException {
936 String propertyName = tokens.canonicalName;
937 String actualName = tokens.actualName;
938
939 if (tokens.keys != null) {
940
941 PropertyTokenHolder getterTokens = new PropertyTokenHolder();
942 getterTokens.canonicalName = tokens.canonicalName;
943 getterTokens.actualName = tokens.actualName;
944 getterTokens.keys = new String[tokens.keys.length - 1];
945 System.arraycopy(tokens.keys, 0, getterTokens.keys, 0, tokens.keys.length - 1);
946 Object propValue;
947 try {
948 propValue = getPropertyValue(getterTokens);
949 }
950 catch (NotReadablePropertyException ex) {
951 throw new NotWritablePropertyException(getRootClass(), this.nestedPath + propertyName,
952 "Cannot access indexed value in property referenced " +
953 "in indexed property path '" + propertyName + "'", ex);
954 }
955
956 String key = tokens.keys[tokens.keys.length - 1];
957 if (propValue == null) {
958
959 if (isAutoGrowNestedPaths()) {
960
961 int lastKeyIndex = tokens.canonicalName.lastIndexOf('[');
962 getterTokens.canonicalName = tokens.canonicalName.substring(0, lastKeyIndex);
963 propValue = setDefaultValue(getterTokens);
964 }
965 else {
966 throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + propertyName,
967 "Cannot access indexed value in property referenced " +
968 "in indexed property path '" + propertyName + "': returned null");
969 }
970 }
971 if (propValue.getClass().isArray()) {
972 PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(actualName);
973 Class<?> requiredType = propValue.getClass().getComponentType();
974 int arrayIndex = Integer.parseInt(key);
975 Object oldValue = null;
976 try {
977 if (isExtractOldValueForEditor() && arrayIndex < Array.getLength(propValue)) {
978 oldValue = Array.get(propValue, arrayIndex);
979 }
980 Object convertedValue = convertIfNecessary(propertyName, oldValue, pv.getValue(),
981 requiredType, TypeDescriptor.nested(property(pd), tokens.keys.length));
982 int length = Array.getLength(propValue);
983 if (arrayIndex >= length && arrayIndex < this.autoGrowCollectionLimit) {
984 Class<?> componentType = propValue.getClass().getComponentType();
985 Object newArray = Array.newInstance(componentType, arrayIndex + 1);
986 System.arraycopy(propValue, 0, newArray, 0, length);
987 setPropertyValue(actualName, newArray);
988 propValue = getPropertyValue(actualName);
989 }
990 Array.set(propValue, arrayIndex, convertedValue);
991 }
992 catch (IndexOutOfBoundsException ex) {
993 throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
994 "Invalid array index in property path '" + propertyName + "'", ex);
995 }
996 }
997 else if (propValue instanceof List) {
998 PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(actualName);
999 Class<?> requiredType = GenericCollectionTypeResolver.getCollectionReturnType(
1000 pd.getReadMethod(), tokens.keys.length);
1001 List<Object> list = (List<Object>) propValue;
1002 int index = Integer.parseInt(key);
1003 Object oldValue = null;
1004 if (isExtractOldValueForEditor() && index < list.size()) {
1005 oldValue = list.get(index);
1006 }
1007 Object convertedValue = convertIfNecessary(propertyName, oldValue, pv.getValue(),
1008 requiredType, TypeDescriptor.nested(property(pd), tokens.keys.length));
1009 int size = list.size();
1010 if (index >= size && index < this.autoGrowCollectionLimit) {
1011 for (int i = size; i < index; i++) {
1012 try {
1013 list.add(null);
1014 }
1015 catch (NullPointerException ex) {
1016 throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
1017 "Cannot set element with index " + index + " in List of size " +
1018 size + ", accessed using property path '" + propertyName +
1019 "': List does not support filling up gaps with null elements");
1020 }
1021 }
1022 list.add(convertedValue);
1023 }
1024 else {
1025 try {
1026 list.set(index, convertedValue);
1027 }
1028 catch (IndexOutOfBoundsException ex) {
1029 throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
1030 "Invalid list index in property path '" + propertyName + "'", ex);
1031 }
1032 }
1033 }
1034 else if (propValue instanceof Map) {
1035 PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(actualName);
1036 Class<?> mapKeyType = GenericCollectionTypeResolver.getMapKeyReturnType(
1037 pd.getReadMethod(), tokens.keys.length);
1038 Class<?> mapValueType = GenericCollectionTypeResolver.getMapValueReturnType(
1039 pd.getReadMethod(), tokens.keys.length);
1040 Map<Object, Object> map = (Map<Object, Object>) propValue;
1041
1042
1043 TypeDescriptor typeDescriptor = TypeDescriptor.valueOf(mapKeyType);
1044 Object convertedMapKey = convertIfNecessary(null, null, key, mapKeyType, typeDescriptor);
1045 Object oldValue = null;
1046 if (isExtractOldValueForEditor()) {
1047 oldValue = map.get(convertedMapKey);
1048 }
1049
1050
1051 Object convertedMapValue = convertIfNecessary(propertyName, oldValue, pv.getValue(),
1052 mapValueType, TypeDescriptor.nested(property(pd), tokens.keys.length));
1053 map.put(convertedMapKey, convertedMapValue);
1054 }
1055 else {
1056 throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
1057 "Property referenced in indexed property path '" + propertyName +
1058 "' is neither an array nor a List nor a Map; returned value was [" + pv.getValue() + "]");
1059 }
1060 }
1061
1062 else {
1063 PropertyDescriptor pd = pv.resolvedDescriptor;
1064 if (pd == null || !pd.getWriteMethod().getDeclaringClass().isInstance(this.object)) {
1065 pd = getCachedIntrospectionResults().getPropertyDescriptor(actualName);
1066 if (pd == null || pd.getWriteMethod() == null) {
1067 if (pv.isOptional()) {
1068 logger.debug("Ignoring optional value for property '" + actualName +
1069 "' - property not found on bean class [" + getRootClass().getName() + "]");
1070 return;
1071 }
1072 else {
1073 PropertyMatches matches = PropertyMatches.forProperty(propertyName, getRootClass());
1074 throw new NotWritablePropertyException(
1075 getRootClass(), this.nestedPath + propertyName,
1076 matches.buildErrorMessage(), matches.getPossibleMatches());
1077 }
1078 }
1079 pv.getOriginalPropertyValue().resolvedDescriptor = pd;
1080 }
1081
1082 Object oldValue = null;
1083 try {
1084 Object originalValue = pv.getValue();
1085 Object valueToApply = originalValue;
1086 if (!Boolean.FALSE.equals(pv.conversionNecessary)) {
1087 if (pv.isConverted()) {
1088 valueToApply = pv.getConvertedValue();
1089 }
1090 else {
1091 if (isExtractOldValueForEditor() && pd.getReadMethod() != null) {
1092 final Method readMethod = pd.getReadMethod();
1093 if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers()) &&
1094 !readMethod.isAccessible()) {
1095 if (System.getSecurityManager()!= null) {
1096 AccessController.doPrivileged(new PrivilegedAction<Object>() {
1097 @Override
1098 public Object run() {
1099 readMethod.setAccessible(true);
1100 return null;
1101 }
1102 });
1103 }
1104 else {
1105 readMethod.setAccessible(true);
1106 }
1107 }
1108 try {
1109 if (System.getSecurityManager() != null) {
1110 oldValue = AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
1111 @Override
1112 public Object run() throws Exception {
1113 return readMethod.invoke(object);
1114 }
1115 }, acc);
1116 }
1117 else {
1118 oldValue = readMethod.invoke(object);
1119 }
1120 }
1121 catch (Exception ex) {
1122 if (ex instanceof PrivilegedActionException) {
1123 ex = ((PrivilegedActionException) ex).getException();
1124 }
1125 if (logger.isDebugEnabled()) {
1126 logger.debug("Could not read previous value of property '" +
1127 this.nestedPath + propertyName + "'", ex);
1128 }
1129 }
1130 }
1131 valueToApply = convertForProperty(
1132 propertyName, oldValue, originalValue, new TypeDescriptor(property(pd)));
1133 }
1134 pv.getOriginalPropertyValue().conversionNecessary = (valueToApply != originalValue);
1135 }
1136 final Method writeMethod = (pd instanceof GenericTypeAwarePropertyDescriptor ?
1137 ((GenericTypeAwarePropertyDescriptor) pd).getWriteMethodForActualAccess() :
1138 pd.getWriteMethod());
1139 if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers()) && !writeMethod.isAccessible()) {
1140 if (System.getSecurityManager()!= null) {
1141 AccessController.doPrivileged(new PrivilegedAction<Object>() {
1142 @Override
1143 public Object run() {
1144 writeMethod.setAccessible(true);
1145 return null;
1146 }
1147 });
1148 }
1149 else {
1150 writeMethod.setAccessible(true);
1151 }
1152 }
1153 final Object value = valueToApply;
1154 if (System.getSecurityManager() != null) {
1155 try {
1156 AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
1157 @Override
1158 public Object run() throws Exception {
1159 writeMethod.invoke(object, value);
1160 return null;
1161 }
1162 }, acc);
1163 }
1164 catch (PrivilegedActionException ex) {
1165 throw ex.getException();
1166 }
1167 }
1168 else {
1169 writeMethod.invoke(this.object, value);
1170 }
1171 }
1172 catch (TypeMismatchException ex) {
1173 throw ex;
1174 }
1175 catch (InvocationTargetException ex) {
1176 PropertyChangeEvent propertyChangeEvent =
1177 new PropertyChangeEvent(this.rootObject, this.nestedPath + propertyName, oldValue, pv.getValue());
1178 if (ex.getTargetException() instanceof ClassCastException) {
1179 throw new TypeMismatchException(propertyChangeEvent, pd.getPropertyType(), ex.getTargetException());
1180 }
1181 else {
1182 throw new MethodInvocationException(propertyChangeEvent, ex.getTargetException());
1183 }
1184 }
1185 catch (Exception ex) {
1186 PropertyChangeEvent pce =
1187 new PropertyChangeEvent(this.rootObject, this.nestedPath + propertyName, oldValue, pv.getValue());
1188 throw new MethodInvocationException(pce, ex);
1189 }
1190 }
1191 }
1192
1193
1194 @Override
1195 public String toString() {
1196 StringBuilder sb = new StringBuilder(getClass().getName());
1197 if (this.object != null) {
1198 sb.append(": wrapping object [").append(ObjectUtils.identityToString(this.object)).append("]");
1199 }
1200 else {
1201 sb.append(": no wrapped object set");
1202 }
1203 return sb.toString();
1204 }
1205
1206
1207 private static class PropertyTokenHolder {
1208
1209 public String canonicalName;
1210
1211 public String actualName;
1212
1213 public String[] keys;
1214 }
1215
1216
1217
1218
1219
1220 @UsesJava8
1221 private static class OptionalUnwrapper {
1222
1223 public static Object unwrap(Object optionalObject) {
1224 Optional<?> optional = (Optional<?>) optionalObject;
1225 Assert.isTrue(optional.isPresent(), "Optional value must be present");
1226 Object result = optional.get();
1227 Assert.isTrue(!(result instanceof Optional), "Multi-level Optional usage not supported");
1228 return result;
1229 }
1230
1231 public static boolean isEmpty(Object optionalObject) {
1232 return !((Optional<?>) optionalObject).isPresent();
1233 }
1234 }
1235
1236 }