KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > springframework > beans > BeanWrapperImpl


1 /*
2  * Copyright 2002-2007 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16
17 package org.springframework.beans;
18
19 import java.beans.PropertyChangeEvent JavaDoc;
20 import java.beans.PropertyDescriptor JavaDoc;
21 import java.lang.reflect.Array JavaDoc;
22 import java.lang.reflect.InvocationTargetException JavaDoc;
23 import java.lang.reflect.Method JavaDoc;
24 import java.lang.reflect.Modifier JavaDoc;
25 import java.util.ArrayList JavaDoc;
26 import java.util.HashMap JavaDoc;
27 import java.util.Iterator JavaDoc;
28 import java.util.List JavaDoc;
29 import java.util.Map JavaDoc;
30 import java.util.Set JavaDoc;
31
32 import org.apache.commons.logging.Log;
33 import org.apache.commons.logging.LogFactory;
34
35 import org.springframework.core.GenericCollectionTypeResolver;
36 import org.springframework.core.JdkVersion;
37 import org.springframework.core.MethodParameter;
38 import org.springframework.util.Assert;
39 import org.springframework.util.ObjectUtils;
40 import org.springframework.util.StringUtils;
41
42 /**
43  * Default {@link BeanWrapper} implementation that should be sufficient
44  * for all typical use cases. Caches introspection results for efficiency.
45  *
46  * <p>Note: Auto-registers default property editors from the
47  * <code>org.springframework.beans.propertyeditors</code> package, which apply
48  * in addition to the JDK's standard PropertyEditors. Applications can call
49  * the {@link #registerCustomEditor(Class, java.beans.PropertyEditor)} method
50  * to register an editor for a particular instance (i.e. they are not shared
51  * across the application). See the base class
52  * {@link PropertyEditorRegistrySupport} for details.
53  *
54  * <p><code>BeanWrapperImpl</code> will convert collection and array values
55  * to the corresponding target collections or arrays, if necessary. Custom
56  * property editors that deal with collections or arrays can either be
57  * written via PropertyEditor's <code>setValue</code>, or against a
58  * comma-delimited String via <code>setAsText</code>, as String arrays are
59  * converted in such a format if the array itself is not assignable.
60  *
61  * @author Rod Johnson
62  * @author Juergen Hoeller
63  * @author Rob Harrop
64  * @since 15 April 2001
65  * @see #registerCustomEditor
66  * @see #setPropertyValues
67  * @see #setPropertyValue
68  * @see #getPropertyValue
69  * @see #getPropertyType
70  * @see BeanWrapper
71  * @see PropertyEditorRegistrySupport
72  */

73 public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWrapper {
74
75     /**
76      * We'll create a lot of these objects, so we don't want a new logger every time.
77      */

78     private static final Log logger = LogFactory.getLog(BeanWrapperImpl.class);
79
80
81     /** The wrapped object */
82     private Object JavaDoc object;
83
84     private String JavaDoc nestedPath = "";
85
86     private Object JavaDoc rootObject;
87
88     private TypeConverterDelegate typeConverterDelegate;
89
90     /**
91      * Cached introspections results for this object, to prevent encountering
92      * the cost of JavaBeans introspection every time.
93      */

94     private CachedIntrospectionResults cachedIntrospectionResults;
95
96     /**
97      * Map with cached nested BeanWrappers: nested path -> BeanWrapper instance.
98      */

99     private Map JavaDoc nestedBeanWrappers;
100
101
102     /**
103      * Create new empty BeanWrapperImpl. Wrapped instance needs to be set afterwards.
104      * Registers default editors.
105      * @see #setWrappedInstance
106      */

107     public BeanWrapperImpl() {
108         this(true);
109     }
110
111     /**
112      * Create new empty BeanWrapperImpl. Wrapped instance needs to be set afterwards.
113      * @param registerDefaultEditors whether to register default editors
114      * (can be suppressed if the BeanWrapper won't need any type conversion)
115      * @see #setWrappedInstance
116      */

117     public BeanWrapperImpl(boolean registerDefaultEditors) {
118         if (registerDefaultEditors) {
119             registerDefaultEditors();
120         }
121         this.typeConverterDelegate = new TypeConverterDelegate(this);
122     }
123
124     /**
125      * Create new BeanWrapperImpl for the given object.
126      * @param object object wrapped by this BeanWrapper
127      */

128     public BeanWrapperImpl(Object JavaDoc object) {
129         registerDefaultEditors();
130         setWrappedInstance(object);
131     }
132
133     /**
134      * Create new BeanWrapperImpl, wrapping a new instance of the specified class.
135      * @param clazz class to instantiate and wrap
136      */

137     public BeanWrapperImpl(Class JavaDoc clazz) {
138         registerDefaultEditors();
139         setWrappedInstance(BeanUtils.instantiateClass(clazz));
140     }
141
142     /**
143      * Create new BeanWrapperImpl for the given object,
144      * registering a nested path that the object is in.
145      * @param object object wrapped by this BeanWrapper
146      * @param nestedPath the nested path of the object
147      * @param rootObject the root object at the top of the path
148      */

149     public BeanWrapperImpl(Object JavaDoc object, String JavaDoc nestedPath, Object JavaDoc rootObject) {
150         registerDefaultEditors();
151         setWrappedInstance(object, nestedPath, rootObject);
152     }
153
154     /**
155      * Create new BeanWrapperImpl for the given object,
156      * registering a nested path that the object is in.
157      * @param object object wrapped by this BeanWrapper
158      * @param nestedPath the nested path of the object
159      * @param superBw the containing BeanWrapper (must not be <code>null</code>)
160      */

161     private BeanWrapperImpl(Object JavaDoc object, String JavaDoc nestedPath, BeanWrapperImpl superBw) {
162         setWrappedInstance(object, nestedPath, superBw.getWrappedInstance());
163         setExtractOldValueForEditor(superBw.isExtractOldValueForEditor());
164     }
165
166
167     //---------------------------------------------------------------------
168
// Implementation of BeanWrapper interface
169
//---------------------------------------------------------------------
170

171     /**
172      * Switch the target object, replacing the cached introspection results only
173      * if the class of the new object is different to that of the replaced object.
174      * @param object the new target object
175      */

176     public void setWrappedInstance(Object JavaDoc object) {
177         setWrappedInstance(object, "", null);
178     }
179
180     /**
181      * Switch the target object, replacing the cached introspection results only
182      * if the class of the new object is different to that of the replaced object.
183      * @param object the new target object
184      * @param nestedPath the nested path of the object
185      * @param rootObject the root object at the top of the path
186      */

187     public void setWrappedInstance(Object JavaDoc object, String JavaDoc nestedPath, Object JavaDoc rootObject) {
188         Assert.notNull(object, "Bean object must not be null");
189         this.object = object;
190         this.nestedPath = (nestedPath != null ? nestedPath : "");
191         this.rootObject = (!"".equals(this.nestedPath) ? rootObject : object);
192         this.nestedBeanWrappers = null;
193         this.typeConverterDelegate = new TypeConverterDelegate(this, object);
194         setIntrospectionClass(object.getClass());
195     }
196
197     public final Object JavaDoc getWrappedInstance() {
198         return this.object;
199     }
200
201     public final Class JavaDoc getWrappedClass() {
202         return (this.object != null ? this.object.getClass() : null);
203     }
204
205     /**
206      * Return the nested path of the object wrapped by this BeanWrapper.
207      */

208     public final String JavaDoc getNestedPath() {
209         return this.nestedPath;
210     }
211
212     /**
213      * Return the root object at the top of the path of this BeanWrapper.
214      * @see #getNestedPath
215      */

216     public final Object JavaDoc getRootInstance() {
217         return this.rootObject;
218     }
219
220     /**
221      * Return the class of the root object at the top of the path of this BeanWrapper.
222      * @see #getNestedPath
223      */

224     public final Class JavaDoc getRootClass() {
225         return (this.rootObject != null ? this.rootObject.getClass() : null);
226     }
227
228     /**
229      * Set the class to introspect.
230      * Needs to be called when the target object changes.
231      * @param clazz the class to introspect
232      */

233     protected void setIntrospectionClass(Class JavaDoc clazz) {
234         if (this.cachedIntrospectionResults == null ||
235                 !this.cachedIntrospectionResults.getBeanClass().equals(clazz)) {
236             this.cachedIntrospectionResults = CachedIntrospectionResults.forClass(clazz);
237         }
238     }
239
240
241     public PropertyDescriptor JavaDoc[] getPropertyDescriptors() {
242         Assert.state(this.cachedIntrospectionResults != null, "BeanWrapper does not hold a bean instance");
243         return this.cachedIntrospectionResults.getBeanInfo().getPropertyDescriptors();
244     }
245
246     public PropertyDescriptor JavaDoc getPropertyDescriptor(String JavaDoc propertyName) throws BeansException {
247         PropertyDescriptor JavaDoc pd = getPropertyDescriptorInternal(propertyName);
248         if (pd == null) {
249             throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
250                     "No property '" + propertyName + "' found");
251         }
252         return pd;
253     }
254
255     /**
256      * Internal version of {@link #getPropertyDescriptor}:
257      * Returns <code>null</code> if not found rather than throwing an exception.
258      * @param propertyName the property to obtain the descriptor for
259      * @return the property descriptor for the specified property,
260      * or <code>null</code> if not found
261      * @throws BeansException in case of introspection failure
262      */

263     protected PropertyDescriptor JavaDoc getPropertyDescriptorInternal(String JavaDoc propertyName) throws BeansException {
264         Assert.state(this.cachedIntrospectionResults != null, "BeanWrapper does not hold a bean instance");
265         Assert.notNull(propertyName, "Property name must not be null");
266         BeanWrapperImpl nestedBw = getBeanWrapperForPropertyPath(propertyName);
267         return nestedBw.cachedIntrospectionResults.getPropertyDescriptor(getFinalPath(nestedBw, propertyName));
268     }
269
270     public Class JavaDoc getPropertyType(String JavaDoc propertyName) throws BeansException {
271         try {
272             PropertyDescriptor JavaDoc pd = getPropertyDescriptorInternal(propertyName);
273             if (pd != null) {
274                 return pd.getPropertyType();
275             }
276             else {
277                 // Maybe an indexed/mapped property...
278
Object JavaDoc value = getPropertyValue(propertyName);
279                 if (value != null) {
280                     return value.getClass();
281                 }
282                 // Check to see if there is a custom editor,
283
// which might give an indication on the desired target type.
284
Class JavaDoc editorType = guessPropertyTypeFromEditors(propertyName);
285                 if (editorType != null) {
286                     return editorType;
287                 }
288             }
289         }
290         catch (InvalidPropertyException ex) {
291             // Consider as not determinable.
292
}
293         return null;
294     }
295
296     public boolean isReadableProperty(String JavaDoc propertyName) {
297         try {
298             PropertyDescriptor JavaDoc pd = getPropertyDescriptorInternal(propertyName);
299             if (pd != null) {
300                 if (pd.getReadMethod() != null) {
301                     return true;
302                 }
303             }
304             else {
305                 // Maybe an indexed/mapped property...
306
getPropertyValue(propertyName);
307                 return true;
308             }
309         }
310         catch (InvalidPropertyException ex) {
311             // Cannot be evaluated, so can't be readable.
312
}
313         return false;
314     }
315
316     public boolean isWritableProperty(String JavaDoc propertyName) {
317         try {
318             PropertyDescriptor JavaDoc pd = getPropertyDescriptorInternal(propertyName);
319             if (pd != null) {
320                 if (pd.getWriteMethod() != null) {
321                     return true;
322                 }
323             }
324             else {
325                 // Maybe an indexed/mapped property...
326
getPropertyValue(propertyName);
327                 return true;
328             }
329         }
330         catch (InvalidPropertyException ex) {
331             // Cannot be evaluated, so can't be writable.
332
}
333         return false;
334     }
335
336
337     //---------------------------------------------------------------------
338
// Implementation of TypeConverter interface
339
//---------------------------------------------------------------------
340

341     /**
342      * @deprecated in favor of <code>convertIfNecessary</code>
343      * @see #convertIfNecessary(Object, Class)
344      */

345     public Object JavaDoc doTypeConversionIfNecessary(Object JavaDoc value, Class JavaDoc requiredType) throws TypeMismatchException {
346         return convertIfNecessary(value, requiredType, null);
347     }
348
349     public Object JavaDoc convertIfNecessary(Object JavaDoc value, Class JavaDoc requiredType) throws TypeMismatchException {
350         return convertIfNecessary(value, requiredType, null);
351     }
352
353     public Object JavaDoc convertIfNecessary(
354             Object JavaDoc value, Class JavaDoc requiredType, MethodParameter methodParam) throws TypeMismatchException {
355         try {
356             return this.typeConverterDelegate.convertIfNecessary(value, requiredType, methodParam);
357         }
358         catch (IllegalArgumentException JavaDoc ex) {
359             throw new TypeMismatchException(value, requiredType, ex);
360         }
361     }
362
363
364     //---------------------------------------------------------------------
365
// Implementation methods
366
//---------------------------------------------------------------------
367

368     /**
369      * Get the last component of the path. Also works if not nested.
370      * @param bw BeanWrapper to work on
371      * @param nestedPath property path we know is nested
372      * @return last component of the path (the property on the target bean)
373      */

374     private String JavaDoc getFinalPath(BeanWrapper bw, String JavaDoc nestedPath) {
375         if (bw == this) {
376             return nestedPath;
377         }
378         return nestedPath.substring(PropertyAccessorUtils.getLastNestedPropertySeparatorIndex(nestedPath) + 1);
379     }
380
381     /**
382      * Recursively navigate to return a BeanWrapper for the nested property path.
383      * @param propertyPath property property path, which may be nested
384      * @return a BeanWrapper for the target bean
385      */

386     protected BeanWrapperImpl getBeanWrapperForPropertyPath(String JavaDoc propertyPath) {
387         int pos = PropertyAccessorUtils.getFirstNestedPropertySeparatorIndex(propertyPath);
388         // Handle nested properties recursively.
389
if (pos > -1) {
390             String JavaDoc nestedProperty = propertyPath.substring(0, pos);
391             String JavaDoc nestedPath = propertyPath.substring(pos + 1);
392             BeanWrapperImpl nestedBw = getNestedBeanWrapper(nestedProperty);
393             return nestedBw.getBeanWrapperForPropertyPath(nestedPath);
394         }
395         else {
396             return this;
397         }
398     }
399
400     /**
401      * Retrieve a BeanWrapper for the given nested property.
402      * Create a new one if not found in the cache.
403      * <p>Note: Caching nested BeanWrappers is necessary now,
404      * to keep registered custom editors for nested properties.
405      * @param nestedProperty property to create the BeanWrapper for
406      * @return the BeanWrapper instance, either cached or newly created
407      */

408     private BeanWrapperImpl getNestedBeanWrapper(String JavaDoc nestedProperty) {
409         if (this.nestedBeanWrappers == null) {
410             this.nestedBeanWrappers = new HashMap JavaDoc();
411         }
412         // Get value of bean property.
413
PropertyTokenHolder tokens = getPropertyNameTokens(nestedProperty);
414         String JavaDoc canonicalName = tokens.canonicalName;
415         Object JavaDoc propertyValue = getPropertyValue(tokens);
416         if (propertyValue == null) {
417             throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + canonicalName);
418         }
419
420         // Lookup cached sub-BeanWrapper, create new one if not found.
421
BeanWrapperImpl nestedBw = (BeanWrapperImpl) this.nestedBeanWrappers.get(canonicalName);
422         if (nestedBw == null || nestedBw.getWrappedInstance() != propertyValue) {
423             if (logger.isTraceEnabled()) {
424                 logger.trace("Creating new nested BeanWrapper for property '" + canonicalName + "'");
425             }
426             nestedBw = newNestedBeanWrapper(propertyValue, this.nestedPath + canonicalName + NESTED_PROPERTY_SEPARATOR);
427             // Inherit all type-specific PropertyEditors.
428
copyDefaultEditorsTo(nestedBw);
429             copyCustomEditorsTo(nestedBw, canonicalName);
430             this.nestedBeanWrappers.put(canonicalName, nestedBw);
431         }
432         else {
433             if (logger.isTraceEnabled()) {
434                 logger.trace("Using cached nested BeanWrapper for property '" + canonicalName + "'");
435             }
436         }
437         return nestedBw;
438     }
439
440     /**
441      * Create a new nested BeanWrapper instance.
442      * <p>Default implementation creates a BeanWrapperImpl instance.
443      * Can be overridden in subclasses to create a BeanWrapperImpl subclass.
444      * @param object object wrapped by this BeanWrapper
445      * @param nestedPath the nested path of the object
446      * @return the nested BeanWrapper instance
447      */

448     protected BeanWrapperImpl newNestedBeanWrapper(Object JavaDoc object, String JavaDoc nestedPath) {
449         return new BeanWrapperImpl(object, nestedPath, this);
450     }
451
452     /**
453      * Parse the given property name into the corresponding property name tokens.
454      * @param propertyName the property name to parse
455      * @return representation of the parsed property tokens
456      */

457     private PropertyTokenHolder getPropertyNameTokens(String JavaDoc propertyName) {
458         PropertyTokenHolder tokens = new PropertyTokenHolder();
459         String JavaDoc actualName = null;
460         List JavaDoc keys = new ArrayList JavaDoc(2);
461         int searchIndex = 0;
462         while (searchIndex != -1) {
463             int keyStart = propertyName.indexOf(PROPERTY_KEY_PREFIX, searchIndex);
464             searchIndex = -1;
465             if (keyStart != -1) {
466                 int keyEnd = propertyName.indexOf(PROPERTY_KEY_SUFFIX, keyStart + PROPERTY_KEY_PREFIX.length());
467                 if (keyEnd != -1) {
468                     if (actualName == null) {
469                         actualName = propertyName.substring(0, keyStart);
470                     }
471                     String JavaDoc key = propertyName.substring(keyStart + PROPERTY_KEY_PREFIX.length(), keyEnd);
472                     if ((key.startsWith("'") && key.endsWith("'")) || (key.startsWith("\"") && key.endsWith("\""))) {
473                         key = key.substring(1, key.length() - 1);
474                     }
475                     keys.add(key);
476                     searchIndex = keyEnd + PROPERTY_KEY_SUFFIX.length();
477                 }
478             }
479         }
480         tokens.actualName = (actualName != null ? actualName : propertyName);
481         tokens.canonicalName = tokens.actualName;
482         if (!keys.isEmpty()) {
483             tokens.canonicalName +=
484                     PROPERTY_KEY_PREFIX +
485                     StringUtils.collectionToDelimitedString(keys, PROPERTY_KEY_SUFFIX + PROPERTY_KEY_PREFIX) +
486                     PROPERTY_KEY_SUFFIX;
487             tokens.keys = StringUtils.toStringArray(keys);
488         }
489         return tokens;
490     }
491
492
493     //---------------------------------------------------------------------
494
// Implementation of PropertyAccessor interface
495
//---------------------------------------------------------------------
496

497     public Object JavaDoc getPropertyValue(String JavaDoc propertyName) throws BeansException {
498         BeanWrapperImpl nestedBw = getBeanWrapperForPropertyPath(propertyName);
499         PropertyTokenHolder tokens = getPropertyNameTokens(getFinalPath(nestedBw, propertyName));
500         return nestedBw.getPropertyValue(tokens);
501     }
502
503     private Object JavaDoc getPropertyValue(PropertyTokenHolder tokens) throws BeansException {
504         String JavaDoc propertyName = tokens.canonicalName;
505         String JavaDoc actualName = tokens.actualName;
506         PropertyDescriptor JavaDoc pd = getPropertyDescriptorInternal(tokens.actualName);
507         if (pd == null || pd.getReadMethod() == null) {
508             throw new NotReadablePropertyException(getRootClass(), this.nestedPath + propertyName);
509         }
510         Method JavaDoc readMethod = pd.getReadMethod();
511         if (logger.isTraceEnabled()) {
512             logger.trace("About to invoke read method [" + readMethod + "] on object of class [" +
513                     this.object.getClass().getName() + "]");
514         }
515         try {
516             if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
517                 readMethod.setAccessible(true);
518             }
519             Object JavaDoc value = readMethod.invoke(this.object, (Object JavaDoc[]) null);
520             if (tokens.keys != null) {
521                 // apply indexes and map keys
522
for (int i = 0; i < tokens.keys.length; i++) {
523                     String JavaDoc key = tokens.keys[i];
524                     if (value == null) {
525                         throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + propertyName,
526                                 "Cannot access indexed value of property referenced in indexed " +
527                                 "property path '" + propertyName + "': returned null");
528                     }
529                     else if (value.getClass().isArray()) {
530                         value = Array.get(value, Integer.parseInt(key));
531                     }
532                     else if (value instanceof List JavaDoc) {
533                         List JavaDoc list = (List JavaDoc) value;
534                         value = list.get(Integer.parseInt(key));
535                     }
536                     else if (value instanceof Set JavaDoc) {
537                         // Apply index to Iterator in case of a Set.
538
Set JavaDoc set = (Set JavaDoc) value;
539                         int index = Integer.parseInt(key);
540                         if (index < 0 || index >= set.size()) {
541                             throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
542                                     "Cannot get element with index " + index + " from Set of size " +
543                                     set.size() + ", accessed using property path '" + propertyName + "'");
544                         }
545                         Iterator JavaDoc it = set.iterator();
546                         for (int j = 0; it.hasNext(); j++) {
547                             Object JavaDoc elem = it.next();
548                             if (j == index) {
549                                 value = elem;
550                                 break;
551                             }
552                         }
553                     }
554                     else if (value instanceof Map JavaDoc) {
555                         Map JavaDoc map = (Map JavaDoc) value;
556                         Class JavaDoc mapKeyType = null;
557                         if (JdkVersion.isAtLeastJava15()) {
558                             mapKeyType = GenericCollectionTypeResolver.getMapKeyReturnType(pd.getReadMethod(), i + 1);
559                         }
560                         // IMPORTANT: Do not pass full property name in here - property editors
561
// must not kick in for map keys but rather only for map values.
562
Object JavaDoc convertedMapKey = this.typeConverterDelegate.convertIfNecessary(key, mapKeyType);
563                         // Pass full property name and old value in here, since we want full
564
// conversion ability for map values.
565
value = map.get(convertedMapKey);
566                     }
567                     else {
568                         throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
569                                 "Property referenced in indexed property path '" + propertyName +
570                                 "' is neither an array nor a List nor a Set nor a Map; returned value was [" + value + "]");
571                     }
572                 }
573             }
574             return value;
575         }
576         catch (InvocationTargetException JavaDoc ex) {
577             throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
578                     "Getter for property '" + actualName + "' threw exception", ex);
579         }
580         catch (IllegalAccessException JavaDoc ex) {
581             throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
582                     "Illegal attempt to get property '" + actualName + "' threw exception", ex);
583         }
584         catch (IndexOutOfBoundsException JavaDoc ex) {
585             throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
586                     "Index of out of bounds in property path '" + propertyName + "'", ex);
587         }
588         catch (NumberFormatException JavaDoc ex) {
589             throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
590                     "Invalid index in property path '" + propertyName + "'", ex);
591         }
592     }
593
594     public void setPropertyValue(String JavaDoc propertyName, Object JavaDoc value) throws BeansException {
595         BeanWrapperImpl nestedBw = null;
596         try {
597             nestedBw = getBeanWrapperForPropertyPath(propertyName);
598         }
599         catch (NotReadablePropertyException ex) {
600             throw new NotWritablePropertyException(getRootClass(), this.nestedPath + propertyName,
601                     "Nested property in path '" + propertyName + "' does not exist", ex);
602         }
603         PropertyTokenHolder tokens = getPropertyNameTokens(getFinalPath(nestedBw, propertyName));
604         nestedBw.setPropertyValue(tokens, value);
605     }
606
607     private void setPropertyValue(PropertyTokenHolder tokens, Object JavaDoc newValue) throws BeansException {
608         String JavaDoc propertyName = tokens.canonicalName;
609
610         if (tokens.keys != null) {
611             // Apply indexes and map keys: fetch value for all keys but the last one.
612
PropertyTokenHolder getterTokens = new PropertyTokenHolder();
613             getterTokens.canonicalName = tokens.canonicalName;
614             getterTokens.actualName = tokens.actualName;
615             getterTokens.keys = new String JavaDoc[tokens.keys.length - 1];
616             System.arraycopy(tokens.keys, 0, getterTokens.keys, 0, tokens.keys.length - 1);
617             Object JavaDoc propValue = null;
618             try {
619                 propValue = getPropertyValue(getterTokens);
620             }
621             catch (NotReadablePropertyException ex) {
622                 throw new NotWritablePropertyException(getRootClass(), this.nestedPath + propertyName,
623                         "Cannot access indexed value in property referenced " +
624                         "in indexed property path '" + propertyName + "'", ex);
625             }
626             // Set value for last key.
627
String JavaDoc key = tokens.keys[tokens.keys.length - 1];
628             if (propValue == null) {
629                 throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + propertyName,
630                         "Cannot access indexed value in property referenced " +
631                         "in indexed property path '" + propertyName + "': returned null");
632             }
633             else if (propValue.getClass().isArray()) {
634                 Class JavaDoc requiredType = propValue.getClass().getComponentType();
635                 int arrayIndex = Integer.parseInt(key);
636                 Object JavaDoc oldValue = null;
637                 try {
638                     if (isExtractOldValueForEditor()) {
639                         oldValue = Array.get(propValue, arrayIndex);
640                     }
641                     Object JavaDoc convertedValue = this.typeConverterDelegate.convertIfNecessary(
642                             propertyName, oldValue, newValue, requiredType);
643                     Array.set(propValue, Integer.parseInt(key), convertedValue);
644                 }
645                 catch (IllegalArgumentException JavaDoc ex) {
646                     PropertyChangeEvent JavaDoc pce =
647                             new PropertyChangeEvent JavaDoc(this.rootObject, this.nestedPath + propertyName, oldValue, newValue);
648                     throw new TypeMismatchException(pce, requiredType, ex);
649                 }
650                 catch (IndexOutOfBoundsException JavaDoc ex) {
651                     throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
652                             "Invalid array index in property path '" + propertyName + "'", ex);
653                 }
654             }
655             else if (propValue instanceof List JavaDoc) {
656                 PropertyDescriptor JavaDoc pd = getPropertyDescriptorInternal(tokens.actualName);
657                 Class JavaDoc requiredType = null;
658                 if (JdkVersion.isAtLeastJava15()) {
659                     requiredType = GenericCollectionTypeResolver.getCollectionReturnType(
660                             pd.getReadMethod(), tokens.keys.length);
661                 }
662                 List JavaDoc list = (List JavaDoc) propValue;
663                 int index = Integer.parseInt(key);
664                 Object JavaDoc oldValue = null;
665                 if (isExtractOldValueForEditor() && index < list.size()) {
666                     oldValue = list.get(index);
667                 }
668                 try {
669                     Object JavaDoc convertedValue = this.typeConverterDelegate.convertIfNecessary(
670                             propertyName, oldValue, newValue, requiredType);
671                     if (index < list.size()) {
672                         list.set(index, convertedValue);
673                     }
674                     else if (index >= list.size()) {
675                         for (int i = list.size(); i < index; i++) {
676                             try {
677                                 list.add(null);
678                             }
679                             catch (NullPointerException JavaDoc ex) {
680                                 throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
681                                         "Cannot set element with index " + index + " in List of size " +
682                                         list.size() + ", accessed using property path '" + propertyName +
683                                         "': List does not support filling up gaps with null elements");
684                             }
685                         }
686                         list.add(convertedValue);
687                     }
688                 }
689                 catch (IllegalArgumentException JavaDoc ex) {
690                     PropertyChangeEvent JavaDoc pce =
691                             new PropertyChangeEvent JavaDoc(this.rootObject, this.nestedPath + propertyName, oldValue, newValue);
692                     throw new TypeMismatchException(pce, requiredType, ex);
693                 }
694             }
695             else if (propValue instanceof Map JavaDoc) {
696                 PropertyDescriptor JavaDoc pd = getPropertyDescriptorInternal(tokens.actualName);
697                 Class JavaDoc mapKeyType = null;
698                 Class JavaDoc mapValueType = null;
699                 if (JdkVersion.isAtLeastJava15()) {
700                     mapKeyType = GenericCollectionTypeResolver.getMapKeyReturnType(
701                             pd.getReadMethod(), tokens.keys.length);
702                     mapValueType = GenericCollectionTypeResolver.getMapValueReturnType(
703                             pd.getReadMethod(), tokens.keys.length);
704                 }
705                 Map JavaDoc map = (Map JavaDoc) propValue;
706                 Object JavaDoc oldValue = null;
707                 if (isExtractOldValueForEditor()) {
708                     oldValue = map.get(key);
709                 }
710                 Object JavaDoc convertedMapKey = null;
711                 Object JavaDoc convertedMapValue = null;
712                 try {
713                     // IMPORTANT: Do not pass full property name in here - property editors
714
// must not kick in for map keys but rather only for map values.
715
convertedMapKey = this.typeConverterDelegate.convertIfNecessary(key, mapKeyType);
716                 }
717                 catch (IllegalArgumentException JavaDoc ex) {
718                     PropertyChangeEvent JavaDoc pce =
719                             new PropertyChangeEvent JavaDoc(this.rootObject, this.nestedPath + propertyName, oldValue, newValue);
720                     throw new TypeMismatchException(pce, mapKeyType, ex);
721                 }
722                 try {
723                     // Pass full property name and old value in here, since we want full
724
// conversion ability for map values.
725
convertedMapValue = this.typeConverterDelegate.convertIfNecessary(
726                             propertyName, oldValue, newValue, mapValueType, null,
727                             new MethodParameter(pd.getReadMethod(), -1, tokens.keys.length + 1));
728                 }
729                 catch (IllegalArgumentException JavaDoc ex) {
730                     PropertyChangeEvent JavaDoc pce =
731                             new PropertyChangeEvent JavaDoc(this.rootObject, this.nestedPath + propertyName, oldValue, newValue);
732                     throw new TypeMismatchException(pce, mapValueType, ex);
733                 }
734                 map.put(convertedMapKey, convertedMapValue);
735             }
736             else {
737                 throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
738                         "Property referenced in indexed property path '" + propertyName +
739                         "' is neither an array nor a List nor a Map; returned value was [" + newValue + "]");
740             }
741         }
742
743         else {
744             PropertyDescriptor JavaDoc pd = getPropertyDescriptorInternal(propertyName);
745             if (pd == null || pd.getWriteMethod() == null) {
746                 PropertyMatches matches = PropertyMatches.forProperty(propertyName, getRootClass());
747                 throw new NotWritablePropertyException(
748                         getRootClass(), this.nestedPath + propertyName,
749                         matches.buildErrorMessage(), matches.getPossibleMatches());
750             }
751
752             Method JavaDoc readMethod = pd.getReadMethod();
753             Method JavaDoc writeMethod = pd.getWriteMethod();
754             Object JavaDoc oldValue = null;
755
756             if (isExtractOldValueForEditor() && readMethod != null) {
757                 if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
758                     readMethod.setAccessible(true);
759                 }
760                 try {
761                     oldValue = readMethod.invoke(this.object, new Object JavaDoc[0]);
762                 }
763                 catch (Exception JavaDoc ex) {
764                     if (logger.isDebugEnabled()) {
765                         logger.debug("Could not read previous value of property '" + this.nestedPath + propertyName + "'", ex);
766                     }
767                 }
768             }
769
770             try {
771                 Object JavaDoc convertedValue = this.typeConverterDelegate.convertIfNecessary(oldValue, newValue, pd);
772
773                 if (pd.getPropertyType().isPrimitive() && (convertedValue == null || "".equals(convertedValue))) {
774                     throw new IllegalArgumentException JavaDoc("Invalid value [" + newValue + "] for property '" +
775                             pd.getName() + "' of primitive type [" + pd.getPropertyType() + "]");
776                 }
777
778                 if (logger.isTraceEnabled()) {
779                     logger.trace("About to invoke write method [" + writeMethod + "] on object of class [" +
780                             this.object.getClass().getName() + "]");
781                 }
782                 if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
783                     writeMethod.setAccessible(true);
784                 }
785                 writeMethod.invoke(this.object, new Object JavaDoc[] {convertedValue});
786                 if (logger.isTraceEnabled()) {
787                     logger.trace("Invoked write method [" + writeMethod + "] with value of type [" +
788                             pd.getPropertyType().getName() + "]");
789                 }
790             }
791             catch (InvocationTargetException JavaDoc ex) {
792                 PropertyChangeEvent JavaDoc propertyChangeEvent =
793                         new PropertyChangeEvent JavaDoc(this.rootObject, this.nestedPath + propertyName, oldValue, newValue);
794                 if (ex.getTargetException() instanceof ClassCastException JavaDoc) {
795                     throw new TypeMismatchException(propertyChangeEvent, pd.getPropertyType(), ex.getTargetException());
796                 }
797                 else {
798                     throw new MethodInvocationException(propertyChangeEvent, ex.getTargetException());
799                 }
800             }
801             catch (IllegalArgumentException JavaDoc ex) {
802                 PropertyChangeEvent JavaDoc pce =
803                         new PropertyChangeEvent JavaDoc(this.rootObject, this.nestedPath + propertyName, oldValue, newValue);
804                 throw new TypeMismatchException(pce, pd.getPropertyType(), ex);
805             }
806             catch (IllegalAccessException JavaDoc ex) {
807                 PropertyChangeEvent JavaDoc pce =
808                         new PropertyChangeEvent JavaDoc(this.rootObject, this.nestedPath + propertyName, oldValue, newValue);
809                 throw new MethodInvocationException(pce, ex);
810             }
811         }
812     }
813
814
815     public String JavaDoc toString() {
816         StringBuffer JavaDoc sb = new StringBuffer JavaDoc(getClass().getName());
817         if (this.object != null) {
818             sb.append(": wrapping object [").append(ObjectUtils.identityToString(this.object)).append("]");
819         }
820         else {
821             sb.append(": no wrapped object set");
822         }
823         return sb.toString();
824     }
825
826
827     //---------------------------------------------------------------------
828
// Inner class for internal use
829
//---------------------------------------------------------------------
830

831     private static class PropertyTokenHolder {
832
833         public String JavaDoc canonicalName;
834
835         public String JavaDoc actualName;
836
837         public String JavaDoc[] keys;
838     }
839
840 }
841
Popular Tags