com.springframework.beans.TypeConverterDelegate.java Source code

Java tutorial

Introduction

Here is the source code for com.springframework.beans.TypeConverterDelegate.java

Source

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

package com.springframework.beans;

import com.springframework.core.convert.ConversionFailedException;
import com.springframework.core.convert.ConversionService;
import com.springframework.core.convert.TypeDescriptor;
import com.springframework.util.ClassUtils;
import com.springframework.util.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.beans.PropertyEditor;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.Map;

/**
 * Internal helper class for converting property values to target types.
 *
 * <p>Works on a given {@link PropertyEditorRegistrySupport} instance.
 * Used as a delegate by {@link BeanWrapperImpl} and {@link SimpleTypeConverter}.
 *
 * @author Juergen Hoeller
 * @author Rob Harrop
 * @author Dave Syer
 * @since 2.0
 * @see BeanWrapperImpl
 */
class TypeConverterDelegate {

    private static final Log logger = LogFactory.getLog(TypeConverterDelegate.class);

    /** Java 8's java.util.Optional.empty() instance */
    private static Object javaUtilOptionalEmpty = null;

    static {
        try {
            Class<?> clazz = ClassUtils.forName("java.util.Optional", TypeConverterDelegate.class.getClassLoader());
            javaUtilOptionalEmpty = ClassUtils.getMethod(clazz, "empty").invoke(null);
        } catch (Exception ex) {
            // Java 8 not available - conversion to Optional not supported then.
        }
    }

    private final PropertyEditorRegistrySupport propertyEditorRegistry;

    private final Object targetObject;

    /**
     * Create a new TypeConverterDelegate for the given editor registry.
     * @param propertyEditorRegistry the editor registry to use
     */
    public TypeConverterDelegate(PropertyEditorRegistrySupport propertyEditorRegistry) {
        this(propertyEditorRegistry, null);
    }

    /**
     * Create a new TypeConverterDelegate for the given editor registry and bean instance.
     * @param propertyEditorRegistry the editor registry to use
     * @param targetObject the target object to work on (as context that can be passed to editors)
     */
    public TypeConverterDelegate(PropertyEditorRegistrySupport propertyEditorRegistry, Object targetObject) {
        this.propertyEditorRegistry = propertyEditorRegistry;
        this.targetObject = targetObject;
    }

    /**
     * Convert the value to the required type (if necessary from a String),
     * for the specified property.
     * @param propertyName name of the property
     * @param oldValue the previous value, if available (may be {@code null})
     * @param newValue the proposed new value
     * @param requiredType the type we must convert to
     * (or {@code null} if not known, for example in case of a collection element)
     * @param typeDescriptor the descriptor for the target property or field
     * @return the new value, possibly the result of type conversion
     * @throws IllegalArgumentException if type conversion failed
     */
    @SuppressWarnings("unchecked")
    public <T> T convertIfNecessary(String propertyName, Object oldValue, Object newValue, Class<T> requiredType,
            TypeDescriptor typeDescriptor) throws IllegalArgumentException {

        Object convertedValue = newValue;

        // Custom editor for this type?
        PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);

        ConversionFailedException firstAttemptEx = null;

        // No custom editor but custom ConversionService specified?
        ConversionService conversionService = this.propertyEditorRegistry.getConversionService();
        //      if (editor == null && conversionService != null && convertedValue != null && typeDescriptor != null) {
        //         TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
        //         TypeDescriptor targetTypeDesc = typeDescriptor;
        //         if (conversionService.canConvert(sourceTypeDesc, targetTypeDesc)) {
        //            try {
        //               return (T) conversionService.convert(convertedValue, sourceTypeDesc, targetTypeDesc);
        //            }
        //            catch (ConversionFailedException ex) {
        //               // fallback to default conversion logic below
        //               firstAttemptEx = ex;
        //            }
        //         }
        //      }

        // Value not of required type?
        if (editor != null
                || (requiredType != null && !ClassUtils.isAssignableValue(requiredType, convertedValue))) {
            //         if (requiredType != null && Collection.class.isAssignableFrom(requiredType) && convertedValue instanceof String) {
            //            TypeDescriptor elementType = typeDescriptor.getElementTypeDescriptor();
            //            if (elementType != null && Enum.class.isAssignableFrom(elementType.getType())) {
            //               convertedValue = StringUtils.commaDelimitedListToStringArray((String) convertedValue);
            //            }
            //         }
            if (editor == null) {
                editor = findDefaultEditor(requiredType);
            }
            convertedValue = doConvertValue(oldValue, convertedValue, requiredType, editor);
        }

        boolean standardConversion = false;

        if (requiredType != null) {
            // Try to apply some standard type conversion rules if appropriate.

            if (convertedValue != null) {
                if (Object.class.equals(requiredType)) {
                    return (T) convertedValue;
                }
                //            if (requiredType.isArray()) {
                //               // Array required -> apply appropriate conversion of elements.
                //               if (convertedValue instanceof String && Enum.class.isAssignableFrom(requiredType.getComponentType())) {
                //                  convertedValue = StringUtils.commaDelimitedListToStringArray((String) convertedValue);
                //               }
                //               return (T) convertToTypedArray(convertedValue, propertyName, requiredType.getComponentType());
                //            }
                //            else if (convertedValue instanceof Collection) {
                //               // Convert elements to target type, if determined.
                //               convertedValue = convertToTypedCollection(
                //                     (Collection<?>) convertedValue, propertyName, requiredType, typeDescriptor);
                //               standardConversion = true;
                //            }
                //            else if (convertedValue instanceof Map) {
                //               // Convert keys and values to respective target type, if determined.
                //               convertedValue = convertToTypedMap(
                //                     (Map<?, ?>) convertedValue, propertyName, requiredType, typeDescriptor);
                //               standardConversion = true;
                //            }
                //            if (convertedValue.getClass().isArray() && Array.getLength(convertedValue) == 1) {
                //               convertedValue = Array.get(convertedValue, 0);
                //               standardConversion = true;
                //            }
                //            if (String.class.equals(requiredType) && ClassUtils.isPrimitiveOrWrapper(convertedValue.getClass())) {
                //               // We can stringify any primitive value...
                //               return (T) convertedValue.toString();
                //            }
                //            else if (convertedValue instanceof String && !requiredType.isInstance(convertedValue)) {
                //               if (firstAttemptEx == null && !requiredType.isInterface() && !requiredType.isEnum()) {
                //                  try {
                //                     Constructor<T> strCtor = requiredType.getConstructor(String.class);
                //                     return BeanUtils.instantiateClass(strCtor, convertedValue);
                //                  }
                //                  catch (NoSuchMethodException ex) {
                //                     // proceed with field lookup
                //                     if (logger.isTraceEnabled()) {
                //                        logger.trace("No String constructor found on type [" + requiredType.getName() + "]", ex);
                //                     }
                //                  }
                //                  catch (Exception ex) {
                //                     if (logger.isDebugEnabled()) {
                //                        logger.debug("Construction via String failed for type [" + requiredType.getName() + "]", ex);
                //                     }
                //                  }
                //               }
                //               String trimmedValue = ((String) convertedValue).trim();
                //               if (requiredType.isEnum() && "".equals(trimmedValue)) {
                //                  // It's an empty enum identifier: reset the enum value to null.
                //                  return null;
                //               }
                //               convertedValue = attemptToConvertStringToEnum(requiredType, trimmedValue, convertedValue);
                //               standardConversion = true;
                //            }
                //         }
                //         else {
                //            // convertedValue == null
                //            if (javaUtilOptionalEmpty != null && requiredType.equals(javaUtilOptionalEmpty.getClass())) {
                //               convertedValue = javaUtilOptionalEmpty;
                //            }
            }

            if (!ClassUtils.isAssignableValue(requiredType, convertedValue)) {
                if (firstAttemptEx != null) {
                    throw firstAttemptEx;
                }
                // Definitely doesn't match: throw IllegalArgumentException/IllegalStateException
                StringBuilder msg = new StringBuilder();
                msg.append("Cannot convert value of type [").append(ClassUtils.getDescriptiveType(newValue));
                msg.append("] to required type [").append(ClassUtils.getQualifiedName(requiredType)).append("]");
                if (propertyName != null) {
                    msg.append(" for property '").append(propertyName).append("'");
                }
                if (editor != null) {
                    msg.append(": PropertyEditor [").append(editor.getClass().getName())
                            .append("] returned inappropriate value of type [")
                            .append(ClassUtils.getDescriptiveType(convertedValue)).append("]");
                    throw new IllegalArgumentException(msg.toString());
                } else {
                    msg.append(": no matching editors or conversion strategy found");
                    throw new IllegalStateException(msg.toString());
                }
            }
        }

        if (firstAttemptEx != null) {
            if (editor == null && !standardConversion && requiredType != null
                    && !Object.class.equals(requiredType)) {
                throw firstAttemptEx;
            }
            logger.debug("Original ConversionService attempt failed - ignored since "
                    + "PropertyEditor based conversion eventually succeeded", firstAttemptEx);
        }

        return (T) convertedValue;
    }

    /**
     * Find a default editor for the given type.
     * @param requiredType the type to find an editor for
     * @return the corresponding editor, or {@code null} if none
     */
    private PropertyEditor findDefaultEditor(Class<?> requiredType) {
        PropertyEditor editor = null;
        if (requiredType != null) {
            // No custom editor -> check BeanWrapperImpl's default editors.
            editor = this.propertyEditorRegistry.getDefaultEditor(requiredType);
            if (editor == null && !String.class.equals(requiredType)) {
                // No BeanWrapper default editor -> check standard JavaBean editor.
                editor = BeanUtils.findEditorByConvention(requiredType);
            }
        }
        return editor;
    }

    /**
     * Convert the value to the required type (if necessary from a String),
     * using the given property editor.
     * @param oldValue the previous value, if available (may be {@code null})
     * @param newValue the proposed new value
     * @param requiredType the type we must convert to
     * (or {@code null} if not known, for example in case of a collection element)
     * @param editor the PropertyEditor to use
     * @return the new value, possibly the result of type conversion
     * @throws IllegalArgumentException if type conversion failed
     */
    private Object doConvertValue(Object oldValue, Object newValue, Class<?> requiredType, PropertyEditor editor) {
        Object convertedValue = newValue;

        if (editor != null && !(convertedValue instanceof String)) {
            // Not a String -> use PropertyEditor's setValue.
            // With standard PropertyEditors, this will return the very same object;
            // we just want to allow special PropertyEditors to override setValue
            // for type conversion from non-String values to the required type.
            try {
                editor.setValue(convertedValue);
                Object newConvertedValue = editor.getValue();
                if (newConvertedValue != convertedValue) {
                    convertedValue = newConvertedValue;
                    // Reset PropertyEditor: It already did a proper conversion.
                    // Don't use it again for a setAsText call.
                    editor = null;
                }
            } catch (Exception ex) {
                if (logger.isDebugEnabled()) {
                    logger.debug(
                            "PropertyEditor [" + editor.getClass().getName() + "] does not support setValue call",
                            ex);
                }
                // Swallow and proceed.
            }
        }

        Object returnValue = convertedValue;

        if (requiredType != null && !requiredType.isArray() && convertedValue instanceof String[]) {
            // Convert String array to a comma-separated String.
            // Only applies if no PropertyEditor converted the String array before.
            // The CSV String will be passed into a PropertyEditor's setAsText method, if any.
            if (logger.isTraceEnabled()) {
                logger.trace("Converting String array to comma-delimited String [" + convertedValue + "]");
            }
            convertedValue = StringUtils.arrayToCommaDelimitedString((String[]) convertedValue);
        }

        if (convertedValue instanceof String) {
            if (editor != null) {
                // Use PropertyEditor's setAsText in case of a String value.
                if (logger.isTraceEnabled()) {
                    logger.trace(
                            "Converting String to [" + requiredType + "] using property editor [" + editor + "]");
                }
                String newTextValue = (String) convertedValue;
                return doConvertTextValue(oldValue, newTextValue, editor);
            } else if (String.class.equals(requiredType)) {
                returnValue = convertedValue;
            }
        }

        return returnValue;
    }

    /**
     * Convert the given text value using the given property editor.
     * @param oldValue the previous value, if available (may be {@code null})
     * @param newTextValue the proposed text value
     * @param editor the PropertyEditor to use
     * @return the converted value
     */
    private Object doConvertTextValue(Object oldValue, String newTextValue, PropertyEditor editor) {
        try {
            editor.setValue(oldValue);
        } catch (Exception ex) {
            if (logger.isDebugEnabled()) {
                logger.debug("PropertyEditor [" + editor.getClass().getName() + "] does not support setValue call",
                        ex);
            }
            // Swallow and proceed.
        }
        editor.setAsText(newTextValue);
        return editor.getValue();
    }
}