KickJava   Java API By Example, From Geeks To Geeks.

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


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.PropertyDescriptor JavaDoc;
20 import java.beans.PropertyEditor JavaDoc;
21 import java.beans.PropertyEditorManager JavaDoc;
22 import java.lang.reflect.Array JavaDoc;
23 import java.lang.reflect.Field JavaDoc;
24 import java.util.Collection JavaDoc;
25 import java.util.Iterator JavaDoc;
26 import java.util.Map JavaDoc;
27
28 import org.apache.commons.logging.Log;
29 import org.apache.commons.logging.LogFactory;
30
31 import org.springframework.core.CollectionFactory;
32 import org.springframework.core.GenericCollectionTypeResolver;
33 import org.springframework.core.JdkVersion;
34 import org.springframework.core.MethodParameter;
35 import org.springframework.util.Assert;
36 import org.springframework.util.ClassUtils;
37 import org.springframework.util.StringUtils;
38
39 /**
40  * Internal helper class for converting property values to target types.
41  *
42  * <p>Works on a given {@link PropertyEditorRegistrySupport} instance.
43  * Used as a delegate by {@link BeanWrapperImpl} and {@link SimpleTypeConverter}.
44  *
45  * @author Juergen Hoeller
46  * @since 2.0
47  * @see BeanWrapperImpl
48  * @see SimpleTypeConverter
49  */

50 class TypeConverterDelegate {
51
52     /**
53      * We'll create a lot of these objects, so we don't want a new logger every time.
54      */

55     private static final Log logger = LogFactory.getLog(TypeConverterDelegate.class);
56
57     private final PropertyEditorRegistrySupport propertyEditorRegistry;
58
59     private final Object JavaDoc targetObject;
60
61
62     /**
63      * Create a new TypeConverterDelegate for the given editor registry.
64      * @param propertyEditorRegistry the editor registry to use
65      */

66     public TypeConverterDelegate(PropertyEditorRegistrySupport propertyEditorRegistry) {
67         this(propertyEditorRegistry, null);
68     }
69
70     /**
71      * Create a new TypeConverterDelegate for the given editor registry and bean instance.
72      * @param propertyEditorRegistry the editor registry to use
73      * @param targetObject the target object to work on (as context that can be passed to editors)
74      */

75     public TypeConverterDelegate(PropertyEditorRegistrySupport propertyEditorRegistry, Object JavaDoc targetObject) {
76         Assert.notNull(propertyEditorRegistry, "Property editor registry must not be null");
77         this.propertyEditorRegistry = propertyEditorRegistry;
78         this.targetObject = targetObject;
79     }
80
81
82     /**
83      * Convert the value to the specified required type.
84      * @param newValue proposed change value
85      * @param requiredType the type we must convert to
86      * (or <code>null</code> if not known, for example in case of a collection element)
87      * @return the new value, possibly the result of type conversion
88      * @throws IllegalArgumentException if type conversion failed
89      */

90     public Object JavaDoc convertIfNecessary(Object JavaDoc newValue, Class JavaDoc requiredType) throws IllegalArgumentException JavaDoc {
91         return convertIfNecessary(null, null, newValue, requiredType, null, null);
92     }
93
94     /**
95      * Convert the value to the specified required type.
96      * @param newValue proposed change value
97      * @param requiredType the type we must convert to
98      * (or <code>null</code> if not known, for example in case of a collection element)
99      * @param methodParam the method parameter that is the target of the conversion
100      * (may be <code>null</code>)
101      * @return the new value, possibly the result of type conversion
102      * @throws IllegalArgumentException if type conversion failed
103      */

104     public Object JavaDoc convertIfNecessary(Object JavaDoc newValue, Class JavaDoc requiredType, MethodParameter methodParam)
105             throws IllegalArgumentException JavaDoc {
106
107         return convertIfNecessary(null, null, newValue, requiredType, null, methodParam);
108     }
109
110     /**
111      * Convert the value to the required type for the specified property.
112      * @param propertyName name of the property
113      * @param oldValue previous value, if available (may be <code>null</code>)
114      * @param newValue proposed change value
115      * @param requiredType the type we must convert to
116      * (or <code>null</code> if not known, for example in case of a collection element)
117      * @return the new value, possibly the result of type conversion
118      * @throws IllegalArgumentException if type conversion failed
119      */

120     public Object JavaDoc convertIfNecessary(
121             String JavaDoc propertyName, Object JavaDoc oldValue, Object JavaDoc newValue, Class JavaDoc requiredType)
122             throws IllegalArgumentException JavaDoc {
123
124         return convertIfNecessary(propertyName, oldValue, newValue, requiredType, null, null);
125     }
126
127     /**
128      * Convert the value to the required type for the specified property.
129      * @param oldValue previous value, if available (may be <code>null</code>)
130      * @param newValue proposed change value
131      * @param descriptor the JavaBeans descriptor for the property
132      * @return the new value, possibly the result of type conversion
133      * @throws IllegalArgumentException if type conversion failed
134      */

135     public Object JavaDoc convertIfNecessary(Object JavaDoc oldValue, Object JavaDoc newValue, PropertyDescriptor JavaDoc descriptor)
136             throws IllegalArgumentException JavaDoc {
137
138         Assert.notNull(descriptor, "PropertyDescriptor must not be null");
139         return convertIfNecessary(
140                 descriptor.getName(), oldValue, newValue, descriptor.getPropertyType(), descriptor,
141                 new MethodParameter(descriptor.getWriteMethod(), 0));
142     }
143
144
145     /**
146      * Convert the value to the required type (if necessary from a String),
147      * for the specified property.
148      * @param propertyName name of the property
149      * @param oldValue previous value, if available (may be <code>null</code>)
150      * @param newValue proposed change value
151      * @param requiredType the type we must convert to
152      * (or <code>null</code> if not known, for example in case of a collection element)
153      * @param descriptor the JavaBeans descriptor for the property
154      * @param methodParam the method parameter that is the target of the conversion
155      * (may be <code>null</code>)
156      * @return the new value, possibly the result of type conversion
157      * @throws IllegalArgumentException if type conversion failed
158      */

159     protected Object JavaDoc convertIfNecessary(
160             String JavaDoc propertyName, Object JavaDoc oldValue, Object JavaDoc newValue, Class JavaDoc requiredType,
161             PropertyDescriptor JavaDoc descriptor, MethodParameter methodParam)
162             throws IllegalArgumentException JavaDoc {
163
164         Object JavaDoc convertedValue = newValue;
165
166         // Custom editor for this type?
167
PropertyEditor JavaDoc pe = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);
168
169         // Value not of required type?
170
if (pe != null || (requiredType != null && !ClassUtils.isAssignableValue(requiredType, convertedValue))) {
171             if (pe == null && descriptor != null) {
172                 if (JdkVersion.isAtLeastJava15()) {
173                     pe = descriptor.createPropertyEditor(this.targetObject);
174                 }
175                 else {
176                     Class JavaDoc editorClass = descriptor.getPropertyEditorClass();
177                     if (editorClass != null) {
178                         pe = (PropertyEditor JavaDoc) BeanUtils.instantiateClass(editorClass);
179                     }
180                 }
181             }
182             if (pe == null && requiredType != null) {
183                 // No custom editor -> check BeanWrapperImpl's default editors.
184
pe = (PropertyEditor JavaDoc) this.propertyEditorRegistry.getDefaultEditor(requiredType);
185                 if (pe == null) {
186                     // No BeanWrapper default editor -> check standard JavaBean editors.
187
pe = PropertyEditorManager.findEditor(requiredType);
188                 }
189             }
190             convertedValue = convertValue(convertedValue, requiredType, pe, oldValue);
191         }
192
193         if (requiredType != null) {
194             // Try to apply some standard type conversion rules if appropriate.
195

196             if (convertedValue != null && requiredType.isArray()) {
197                 // Array required -> apply appropriate conversion of elements.
198
return convertToTypedArray(convertedValue, propertyName, requiredType.getComponentType());
199             }
200             else if (convertedValue instanceof Collection JavaDoc && Collection JavaDoc.class.isAssignableFrom(requiredType)) {
201                 // Convert elements to target type, if determined.
202
return convertToTypedCollection((Collection JavaDoc) convertedValue, propertyName, methodParam);
203             }
204             else if (convertedValue instanceof Map JavaDoc && Map JavaDoc.class.isAssignableFrom(requiredType)) {
205                 // Convert keys and values to respective target type, if determined.
206
return convertToTypedMap((Map JavaDoc) convertedValue, propertyName, methodParam);
207             }
208             else if (convertedValue instanceof String JavaDoc && !requiredType.isInstance(convertedValue)) {
209                 // Try field lookup as fallback: for JDK 1.5 enum or custom enum
210
// with values defined as static fields. Resulting value still needs
211
// to be checked, hence we don't return it right away.
212
try {
213                     Field JavaDoc enumField = requiredType.getField((String JavaDoc) convertedValue);
214                     convertedValue = enumField.get(null);
215                 }
216                 catch (Throwable JavaDoc ex) {
217                     if (logger.isTraceEnabled()) {
218                         logger.trace("Field [" + convertedValue + "] isn't an enum value", ex);
219                     }
220                 }
221             }
222
223             if (!ClassUtils.isAssignableValue(requiredType, convertedValue)) {
224                 // Definitely doesn't match: throw IllegalArgumentException.
225
throw new IllegalArgumentException JavaDoc("Cannot convert value of type [" +
226                         (newValue != null ? ClassUtils.getQualifiedName(newValue.getClass()) : null) +
227                         "] to required type [" + ClassUtils.getQualifiedName(requiredType) + "] for property '" +
228                         propertyName + "': no matching editors or conversion strategy found");
229             }
230         }
231
232         return convertedValue;
233     }
234
235     protected Object JavaDoc convertValue(Object JavaDoc newValue, Class JavaDoc requiredType, PropertyEditor JavaDoc pe, Object JavaDoc oldValue) {
236         Object JavaDoc convertedValue = newValue;
237
238         if (pe != null && !(convertedValue instanceof String JavaDoc)) {
239             // Not a String -> use PropertyEditor's setValue.
240
// With standard PropertyEditors, this will return the very same object;
241
// we just want to allow special PropertyEditors to override setValue
242
// for type conversion from non-String values to the required type.
243
pe.setValue(convertedValue);
244             Object JavaDoc newConvertedValue = pe.getValue();
245             if (newConvertedValue != convertedValue) {
246                 convertedValue = newConvertedValue;
247                 // Reset PropertyEditor: It already did a proper conversion.
248
// Don't use it again for a setAsText call.
249
pe = null;
250             }
251         }
252
253         if (requiredType != null && !requiredType.isArray() && convertedValue instanceof String JavaDoc[]) {
254             // Convert String array to a comma-separated String.
255
// Only applies if no PropertyEditor converted the String array before.
256
// The CSV String will be passed into a PropertyEditor's setAsText method, if any.
257
if (logger.isTraceEnabled()) {
258                 logger.trace("Converting String array to comma-delimited String [" + convertedValue + "]");
259             }
260             convertedValue = StringUtils.arrayToCommaDelimitedString((String JavaDoc[]) convertedValue);
261         }
262
263         if (pe != null && convertedValue instanceof String JavaDoc) {
264             // Use PropertyEditor's setAsText in case of a String value.
265
if (logger.isTraceEnabled()) {
266                 logger.trace("Converting String to [" + requiredType + "] using property editor [" + pe + "]");
267             }
268             pe.setValue(oldValue);
269             pe.setAsText((String JavaDoc) convertedValue);
270             convertedValue = pe.getValue();
271         }
272
273         return convertedValue;
274     }
275
276     protected Object JavaDoc convertToTypedArray(Object JavaDoc input, String JavaDoc propertyName, Class JavaDoc componentType) {
277         if (input instanceof Collection JavaDoc) {
278             // Convert Collection elements to array elements.
279
Collection JavaDoc coll = (Collection JavaDoc) input;
280             Object JavaDoc result = Array.newInstance(componentType, coll.size());
281             int i = 0;
282             for (Iterator JavaDoc it = coll.iterator(); it.hasNext(); i++) {
283                 Object JavaDoc value = convertIfNecessary(
284                         buildIndexedPropertyName(propertyName, i), null, it.next(), componentType);
285                 Array.set(result, i, value);
286             }
287             return result;
288         }
289         else if (input.getClass().isArray()) {
290             // Convert array elements, if necessary.
291
if (componentType.equals(input.getClass().getComponentType()) &&
292                     !this.propertyEditorRegistry.hasCustomEditorForElement(componentType, propertyName)) {
293                 return input;
294             }
295             int arrayLength = Array.getLength(input);
296             Object JavaDoc result = Array.newInstance(componentType, arrayLength);
297             for (int i = 0; i < arrayLength; i++) {
298                 Object JavaDoc value = convertIfNecessary(
299                         buildIndexedPropertyName(propertyName, i), null, Array.get(input, i), componentType);
300                 Array.set(result, i, value);
301             }
302             return result;
303         }
304         else {
305             // A plain value: convert it to an array with a single component.
306
Object JavaDoc result = Array.newInstance(componentType, 1);
307             Object JavaDoc value = convertIfNecessary(
308                     buildIndexedPropertyName(propertyName, 0), null, input, componentType);
309             Array.set(result, 0, value);
310             return result;
311         }
312     }
313
314     protected Collection JavaDoc convertToTypedCollection(
315             Collection JavaDoc original, String JavaDoc propertyName, MethodParameter methodParam) {
316
317         Class JavaDoc elementType = null;
318         if (methodParam != null && JdkVersion.isAtLeastJava15()) {
319             elementType = GenericCollectionTypeResolver.getCollectionParameterType(methodParam);
320         }
321         Collection JavaDoc convertedCopy = null;
322         Iterator JavaDoc it = null;
323         try {
324             convertedCopy = CollectionFactory.createApproximateCollection(original, original.size());
325             it = original.iterator();
326         }
327         catch (Throwable JavaDoc ex) {
328             if (logger.isDebugEnabled()) {
329                 logger.debug("Cannot access Collection of type [" + original.getClass().getName() +
330                         "] - injecting original Collection as-is", ex);
331             }
332             return original;
333         }
334         boolean actuallyConverted = false;
335         int i = 0;
336         for (; it.hasNext(); i++) {
337             Object JavaDoc element = it.next();
338             String JavaDoc indexedPropertyName = buildIndexedPropertyName(propertyName, i);
339             if (methodParam != null) {
340                 methodParam.increaseNestingLevel();
341             }
342             Object JavaDoc convertedElement =
343                     convertIfNecessary(indexedPropertyName, null, element, elementType, null, methodParam);
344             if (methodParam != null) {
345                 methodParam.decreaseNestingLevel();
346             }
347             convertedCopy.add(convertedElement);
348             actuallyConverted = actuallyConverted || (element != convertedElement);
349         }
350         return (actuallyConverted ? convertedCopy : original);
351     }
352
353     protected Map JavaDoc convertToTypedMap(Map JavaDoc original, String JavaDoc propertyName, MethodParameter methodParam) {
354         Class JavaDoc keyType = null;
355         Class JavaDoc valueType = null;
356         if (methodParam != null && JdkVersion.isAtLeastJava15()) {
357             keyType = GenericCollectionTypeResolver.getMapKeyParameterType(methodParam);
358             valueType = GenericCollectionTypeResolver.getMapValueParameterType(methodParam);
359         }
360         Map JavaDoc convertedCopy = null;
361         Iterator JavaDoc it = null;
362         try {
363             convertedCopy = CollectionFactory.createApproximateMap(original, original.size());
364             it = original.entrySet().iterator();
365         }
366         catch (Throwable JavaDoc ex) {
367             if (logger.isDebugEnabled()) {
368                 logger.debug("Cannot access Map of type [" + original.getClass().getName() +
369                         "] - injecting original Map as-is", ex);
370             }
371             return original;
372         }
373         boolean actuallyConverted = false;
374         while (it.hasNext()) {
375             Map.Entry JavaDoc entry = (Map.Entry JavaDoc) it.next();
376             Object JavaDoc key = entry.getKey();
377             Object JavaDoc value = entry.getValue();
378             String JavaDoc keyedPropertyName = buildKeyedPropertyName(propertyName, key);
379             if (methodParam != null) {
380                 methodParam.increaseNestingLevel();
381                 methodParam.setTypeIndexForCurrentLevel(0);
382             }
383             Object JavaDoc convertedKey = convertIfNecessary(keyedPropertyName, null, key, keyType, null, methodParam);
384             if (methodParam != null) {
385                 methodParam.setTypeIndexForCurrentLevel(1);
386             }
387             Object JavaDoc convertedValue = convertIfNecessary(keyedPropertyName, null, value, valueType, null, methodParam);
388             if (methodParam != null) {
389                 methodParam.decreaseNestingLevel();
390             }
391             convertedCopy.put(convertedKey, convertedValue);
392             actuallyConverted = actuallyConverted || (key != convertedKey) || (value != convertedValue);
393         }
394         return (actuallyConverted ? convertedCopy : original);
395     }
396
397     private String JavaDoc buildIndexedPropertyName(String JavaDoc propertyName, int index) {
398         return (propertyName != null ?
399                 propertyName + PropertyAccessor.PROPERTY_KEY_PREFIX + index + PropertyAccessor.PROPERTY_KEY_SUFFIX :
400                 null);
401     }
402
403     private String JavaDoc buildKeyedPropertyName(String JavaDoc propertyName, Object JavaDoc key) {
404         return (propertyName != null ?
405                 propertyName + PropertyAccessor.PROPERTY_KEY_PREFIX + key + PropertyAccessor.PROPERTY_KEY_SUFFIX :
406                 null);
407     }
408
409 }
410
Popular Tags