org.springframework.core.convert.support.GenericConversionService.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.core.convert.support.GenericConversionService.java

Source

/*
 * Copyright 2002-2011 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 org.springframework.core.convert.support;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.core.GenericTypeResolver;
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.ConverterNotFoundException;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.ConditionalGenericConverter;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConverterFactory;
import org.springframework.core.convert.converter.ConverterRegistry;
import org.springframework.core.convert.converter.GenericConverter;
import org.springframework.core.style.StylerUtils;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;

/**
 * Base {@link ConversionService} implementation suitable for use in most environments.
 * Implements {@link ConverterRegistry} as registration API.
 *
 * @author Keith Donald
 * @author Juergen Hoeller
 * @since 3.0
 */
public class GenericConversionService implements ConversionService, ConverterRegistry {

    private static final GenericConverter NO_OP_CONVERTER = new GenericConverter() {
        public Set<ConvertiblePair> getConvertibleTypes() {
            return null;
        }

        public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
            return source;
        }

        public String toString() {
            return "NO_OP";
        }
    };

    private static final GenericConverter NO_MATCH = new GenericConverter() {
        public Set<ConvertiblePair> getConvertibleTypes() {
            throw new UnsupportedOperationException();
        }

        public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
            throw new UnsupportedOperationException();
        }

        public String toString() {
            return "NO_MATCH";
        }
    };

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

    private final Map<Class<?>, Map<Class<?>, MatchableConverters>> converters = new HashMap<Class<?>, Map<Class<?>, MatchableConverters>>(
            36);

    private final Map<ConverterCacheKey, GenericConverter> converterCache = new ConcurrentHashMap<ConverterCacheKey, GenericConverter>();

    // implementing ConverterRegistry

    public void addConverter(Converter<?, ?> converter) {
        GenericConverter.ConvertiblePair typeInfo = getRequiredTypeInfo(converter, Converter.class);
        if (typeInfo == null) {
            throw new IllegalArgumentException("Unable to the determine sourceType <S> and targetType <T> which "
                    + "your Converter<S, T> converts between; declare these generic types.");
        }
        addConverter(new ConverterAdapter(typeInfo, converter));
    }

    public void addConverterFactory(ConverterFactory<?, ?> converterFactory) {
        GenericConverter.ConvertiblePair typeInfo = getRequiredTypeInfo(converterFactory, ConverterFactory.class);
        if (typeInfo == null) {
            throw new IllegalArgumentException("Unable to the determine sourceType <S> and targetRangeType R which "
                    + "your ConverterFactory<S, R> converts between; declare these generic types.");
        }
        addConverter(new ConverterFactoryAdapter(typeInfo, converterFactory));
    }

    public void addConverter(GenericConverter converter) {
        Set<GenericConverter.ConvertiblePair> convertibleTypes = converter.getConvertibleTypes();
        for (GenericConverter.ConvertiblePair convertibleType : convertibleTypes) {
            getMatchableConverters(convertibleType.getSourceType(), convertibleType.getTargetType()).add(converter);
        }
        invalidateCache();
    }

    public void removeConvertible(Class<?> sourceType, Class<?> targetType) {
        getSourceConverterMap(sourceType).remove(targetType);
        invalidateCache();
    }

    // implementing ConversionService

    public boolean canConvert(Class<?> sourceType, Class<?> targetType) {
        return canConvert(TypeDescriptor.valueOf(sourceType), TypeDescriptor.valueOf(targetType));
    }

    @SuppressWarnings("unchecked")
    public <T> T convert(Object source, Class<T> targetType) {
        return (T) convert(source, TypeDescriptor.forObject(source), TypeDescriptor.valueOf(targetType));
    }

    public boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType) {
        assertNotNull(sourceType, targetType);
        if (logger.isTraceEnabled()) {
            logger.trace("Checking if I can convert " + sourceType + " to " + targetType);
        }
        if (sourceType == TypeDescriptor.NULL || targetType == TypeDescriptor.NULL) {
            logger.trace("Yes, I can convert");
            return true;
        }
        GenericConverter converter = getConverter(sourceType, targetType);
        if (converter != null) {
            logger.trace("Yes, I can convert");
            return true;
        } else {
            logger.trace("No, I cannot convert");
            return false;
        }
    }

    public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
        assertNotNull(sourceType, targetType);
        if (logger.isDebugEnabled()) {
            logger.debug(
                    "Converting value " + StylerUtils.style(source) + " of " + sourceType + " to " + targetType);
        }
        if (sourceType == TypeDescriptor.NULL) {
            Assert.isTrue(source == null, "The value must be null if sourceType == TypeDescriptor.NULL");
            Object result = convertNullSource(sourceType, targetType);
            if (logger.isDebugEnabled()) {
                logger.debug("Converted to " + StylerUtils.style(result));
            }
            return result;
        }
        if (targetType == TypeDescriptor.NULL) {
            logger.debug("Converted to null");
            return null;
        }
        Assert.isTrue(source == null || sourceType.getObjectType().isInstance(source));
        GenericConverter converter = getConverter(sourceType, targetType);
        if (converter == null) {
            if (source == null || sourceType.isAssignableTo(targetType)) {
                logger.debug("No converter found - returning assignable source object as-is");
                return source;
            } else {
                throw new ConverterNotFoundException(sourceType, targetType);
            }
        }
        Object result = ConversionUtils.invokeConverter(converter, source, sourceType, targetType);
        if (logger.isDebugEnabled()) {
            logger.debug("Converted to " + StylerUtils.style(result));
        }
        return result;
    }

    public String toString() {
        List<String> converterStrings = new ArrayList<String>();
        for (Map<Class<?>, MatchableConverters> targetConverters : this.converters.values()) {
            for (MatchableConverters matchable : targetConverters.values()) {
                converterStrings.add(matchable.toString());
            }
        }
        Collections.sort(converterStrings);
        StringBuilder builder = new StringBuilder();
        builder.append("ConversionService converters = ").append("\n");
        for (String converterString : converterStrings) {
            builder.append("\t");
            builder.append(converterString);
            builder.append("\n");
        }
        return builder.toString();
    }

    // subclassing hooks

    /**
     * Template method to convert a null source.
     * <p>Default implementation returns <code>null</code>.
     * Throws a {@link ConversionFailedException} if the targetType is a primitive type,
     * as <code>null</code> cannot be assigned to a primitive type.
     * Subclasses may override to return custom null objects for specific target types.
     * @param sourceType the sourceType to convert from
     * @param targetType the targetType to convert to
     * @return the converted null object
     */
    protected Object convertNullSource(TypeDescriptor sourceType, TypeDescriptor targetType) {
        if (targetType.isPrimitive()) {
            throw new ConversionFailedException(sourceType, targetType, null,
                    new IllegalArgumentException("A null value cannot be assigned to a primitive type"));
        }
        return null;
    }

    /**
     * Hook method to lookup the converter for a given sourceType/targetType pair.
     * First queries this ConversionService's converter cache.
     * On a cache miss, then performs an exhaustive search for a matching converter.
     * If no converter matches, returns the default converter.
     * Subclasses may override.
     * @param sourceType the source type to convert from
     * @param targetType the target type to convert to
     * @return the generic converter that will perform the conversion, or <code>null</code> if no suitable converter was found
     * @see #getDefaultConverter(TypeDescriptor, TypeDescriptor)
     */
    protected GenericConverter getConverter(TypeDescriptor sourceType, TypeDescriptor targetType) {
        ConverterCacheKey key = new ConverterCacheKey(sourceType, targetType);
        GenericConverter converter = this.converterCache.get(key);
        if (converter != null) {
            if (logger.isTraceEnabled()) {
                logger.trace("Matched cached converter " + converter);
            }
            return (converter != NO_MATCH ? converter : null);
        } else {
            converter = findConverterForClassPair(sourceType, targetType);
            if (converter != null) {
                if (logger.isTraceEnabled()) {
                    logger.trace("Caching under " + key);
                }
                this.converterCache.put(key, converter);
                return converter;
            }
            converter = getDefaultConverter(sourceType, targetType);
            if (converter != null) {
                if (logger.isTraceEnabled()) {
                    logger.trace("Caching under " + key);
                }
                this.converterCache.put(key, converter);
                return converter;
            }
            if (logger.isTraceEnabled()) {
                logger.trace("Caching NO_MATCH under " + key);
            }
            this.converterCache.put(key, NO_MATCH);
            return null;
        }
    }

    /**
     * Return the default converter if no converter is found for the given sourceType/targetType pair.
     * Returns a NO_OP Converter if the sourceType is assignable to the targetType.
     * Returns <code>null</code> otherwise, indicating no suitable converter could be found.
     * Subclasses may override.
     * @param sourceType the source type to convert from
     * @param targetType the target type to convert to
     * @return the default generic converter that will perform the conversion
     */
    protected GenericConverter getDefaultConverter(TypeDescriptor sourceType, TypeDescriptor targetType) {
        if (sourceType.isAssignableTo(targetType)) {
            logger.trace("Matched default NO_OP_CONVERTER");
            return NO_OP_CONVERTER;
        } else {
            return null;
        }
    }

    // internal helpers

    private GenericConverter.ConvertiblePair getRequiredTypeInfo(Object converter, Class<?> genericIfc) {
        Class<?>[] args = GenericTypeResolver.resolveTypeArguments(converter.getClass(), genericIfc);
        return (args != null ? new GenericConverter.ConvertiblePair(args[0], args[1]) : null);
    }

    private MatchableConverters getMatchableConverters(Class<?> sourceType, Class<?> targetType) {
        Map<Class<?>, MatchableConverters> sourceMap = getSourceConverterMap(sourceType);
        MatchableConverters matchable = sourceMap.get(targetType);
        if (matchable == null) {
            matchable = new MatchableConverters();
            sourceMap.put(targetType, matchable);
        }
        return matchable;
    }

    private void invalidateCache() {
        this.converterCache.clear();
    }

    private Map<Class<?>, MatchableConverters> getSourceConverterMap(Class<?> sourceType) {
        Map<Class<?>, MatchableConverters> sourceMap = converters.get(sourceType);
        if (sourceMap == null) {
            sourceMap = new HashMap<Class<?>, MatchableConverters>();
            this.converters.put(sourceType, sourceMap);
        }
        return sourceMap;
    }

    private void assertNotNull(TypeDescriptor sourceType, TypeDescriptor targetType) {
        Assert.notNull(sourceType, "The sourceType to convert to is required");
        Assert.notNull(targetType, "The targetType to convert to is required");
    }

    private GenericConverter findConverterForClassPair(TypeDescriptor sourceType, TypeDescriptor targetType) {
        Class<?> sourceObjectType = sourceType.getObjectType();
        if (sourceObjectType.isInterface()) {
            LinkedList<Class<?>> classQueue = new LinkedList<Class<?>>();
            classQueue.addFirst(sourceObjectType);
            while (!classQueue.isEmpty()) {
                Class<?> currentClass = classQueue.removeLast();
                if (logger.isTraceEnabled()) {
                    logger.trace("Searching for converters indexed by sourceType [" + currentClass.getName() + "]");
                }
                Map<Class<?>, MatchableConverters> converters = getTargetConvertersForSource(currentClass);
                GenericConverter converter = getMatchingConverterForTarget(sourceType, targetType, converters);
                if (converter != null) {
                    return converter;
                }
                Class<?>[] interfaces = currentClass.getInterfaces();
                for (Class<?> ifc : interfaces) {
                    classQueue.addFirst(ifc);
                }
            }
            Map<Class<?>, MatchableConverters> objectConverters = getTargetConvertersForSource(Object.class);
            return getMatchingConverterForTarget(sourceType, targetType, objectConverters);
        } else {
            LinkedList<Class<?>> classQueue = new LinkedList<Class<?>>();
            classQueue.addFirst(sourceObjectType);
            while (!classQueue.isEmpty()) {
                Class<?> currentClass = classQueue.removeLast();
                if (logger.isTraceEnabled()) {
                    logger.trace("Searching for converters indexed by sourceType [" + currentClass.getName() + "]");
                }
                Map<Class<?>, MatchableConverters> converters = getTargetConvertersForSource(currentClass);
                GenericConverter converter = getMatchingConverterForTarget(sourceType, targetType, converters);
                if (converter != null) {
                    return converter;
                }
                if (currentClass.isArray()) {
                    Class<?> componentType = ClassUtils
                            .resolvePrimitiveIfNecessary(currentClass.getComponentType());
                    if (componentType.getSuperclass() != null) {
                        classQueue.addFirst(Array.newInstance(componentType.getSuperclass(), 0).getClass());
                    } else if (componentType.isInterface()) {
                        classQueue.addFirst(Object[].class);
                    }
                } else {
                    Class<?>[] interfaces = currentClass.getInterfaces();
                    for (Class<?> ifc : interfaces) {
                        addInterfaceHierarchy(ifc, classQueue);
                    }
                    if (currentClass.getSuperclass() != null) {
                        classQueue.addFirst(currentClass.getSuperclass());
                    }
                }
            }
            return null;
        }
    }

    private Map<Class<?>, MatchableConverters> getTargetConvertersForSource(Class<?> sourceType) {
        Map<Class<?>, MatchableConverters> converters = this.converters.get(sourceType);
        if (converters == null) {
            converters = Collections.emptyMap();
        }
        return converters;
    }

    private GenericConverter getMatchingConverterForTarget(TypeDescriptor sourceType, TypeDescriptor targetType,
            Map<Class<?>, MatchableConverters> converters) {

        Class<?> targetObjectType = targetType.getObjectType();
        if (targetObjectType.isInterface()) {
            LinkedList<Class<?>> classQueue = new LinkedList<Class<?>>();
            classQueue.addFirst(targetObjectType);
            while (!classQueue.isEmpty()) {
                Class<?> currentClass = classQueue.removeLast();
                if (logger.isTraceEnabled()) {
                    logger.trace("and indexed by targetType [" + currentClass.getName() + "]");
                }
                MatchableConverters matchable = converters.get(currentClass);
                GenericConverter converter = matchConverter(matchable, sourceType, targetType);
                if (converter != null) {
                    return converter;
                }
                Class<?>[] interfaces = currentClass.getInterfaces();
                for (Class<?> ifc : interfaces) {
                    classQueue.addFirst(ifc);
                }
            }
            if (logger.isTraceEnabled()) {
                logger.trace("and indexed by [java.lang.Object]");
            }
            return matchConverter(converters.get(Object.class), sourceType, targetType);
        } else {
            LinkedList<Class<?>> classQueue = new LinkedList<Class<?>>();
            classQueue.addFirst(targetObjectType);
            while (!classQueue.isEmpty()) {
                Class<?> currentClass = classQueue.removeLast();
                if (logger.isTraceEnabled()) {
                    logger.trace("and indexed by targetType [" + currentClass.getName() + "]");
                }
                MatchableConverters matchable = converters.get(currentClass);
                GenericConverter converter = matchConverter(matchable, sourceType, targetType);
                if (converter != null) {
                    return converter;
                }
                if (currentClass.isArray()) {
                    Class<?> componentType = ClassUtils
                            .resolvePrimitiveIfNecessary(currentClass.getComponentType());
                    if (componentType.getSuperclass() != null) {
                        classQueue.addFirst(Array.newInstance(componentType.getSuperclass(), 0).getClass());
                    } else if (componentType.isInterface()) {
                        classQueue.addFirst(Object[].class);
                    }
                } else {
                    Class<?>[] interfaces = currentClass.getInterfaces();
                    for (Class<?> ifc : interfaces) {
                        addInterfaceHierarchy(ifc, classQueue);
                    }
                    if (currentClass.getSuperclass() != null) {
                        classQueue.addFirst(currentClass.getSuperclass());
                    }
                }
            }
            return null;
        }
    }

    private void addInterfaceHierarchy(Class<?> ifc, LinkedList<Class<?>> classQueue) {
        classQueue.addFirst(ifc);
        for (Class<?> inheritedIfc : ifc.getInterfaces()) {
            addInterfaceHierarchy(inheritedIfc, classQueue);
        }
    }

    private GenericConverter matchConverter(MatchableConverters matchable, TypeDescriptor sourceFieldType,
            TypeDescriptor targetFieldType) {

        if (matchable == null) {
            return null;
        }
        if (logger.isTraceEnabled()) {
            logger.trace("Found matchable converters " + matchable);
        }
        return matchable.matchConverter(sourceFieldType, targetFieldType);
    }

    @SuppressWarnings("unchecked")
    private final class ConverterAdapter implements GenericConverter {

        private final ConvertiblePair typeInfo;

        private final Converter converter;

        public ConverterAdapter(ConvertiblePair typeInfo, Converter<?, ?> converter) {
            this.converter = converter;
            this.typeInfo = typeInfo;
        }

        public Set<ConvertiblePair> getConvertibleTypes() {
            return Collections.singleton(this.typeInfo);
        }

        public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
            if (source == null) {
                return convertNullSource(sourceType, targetType);
            }
            return this.converter.convert(source);
        }

        public String toString() {
            return this.typeInfo.getSourceType().getName() + " -> " + this.typeInfo.getTargetType().getName()
                    + " : " + this.converter.toString();
        }
    }

    @SuppressWarnings("unchecked")
    private final class ConverterFactoryAdapter implements GenericConverter {

        private final ConvertiblePair typeInfo;

        private final ConverterFactory converterFactory;

        public ConverterFactoryAdapter(ConvertiblePair typeInfo, ConverterFactory<?, ?> converterFactory) {
            this.converterFactory = converterFactory;
            this.typeInfo = typeInfo;
        }

        public Set<ConvertiblePair> getConvertibleTypes() {
            return Collections.singleton(this.typeInfo);
        }

        public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
            if (source == null) {
                return convertNullSource(sourceType, targetType);
            }
            return this.converterFactory.getConverter(targetType.getObjectType()).convert(source);
        }

        public String toString() {
            return this.typeInfo.getSourceType().getName() + " -> " + this.typeInfo.getTargetType().getName()
                    + " : " + this.converterFactory.toString();
        }
    }

    private static class MatchableConverters {

        private LinkedList<ConditionalGenericConverter> conditionalConverters;

        private GenericConverter defaultConverter;

        public void add(GenericConverter converter) {
            if (converter instanceof ConditionalGenericConverter) {
                if (this.conditionalConverters == null) {
                    this.conditionalConverters = new LinkedList<ConditionalGenericConverter>();
                }
                this.conditionalConverters.addFirst((ConditionalGenericConverter) converter);
            } else {
                this.defaultConverter = converter;
            }
        }

        public GenericConverter matchConverter(TypeDescriptor sourceType, TypeDescriptor targetType) {
            if (this.conditionalConverters != null) {
                for (ConditionalGenericConverter conditional : this.conditionalConverters) {
                    if (logger.isTraceEnabled()) {
                        logger.trace("Matching " + conditional);
                    }
                    if (conditional.matches(sourceType, targetType)) {
                        if (logger.isTraceEnabled()) {
                            logger.trace("Matched converter " + conditional);
                        }
                        return conditional;
                    } else {
                        if (logger.isTraceEnabled()) {
                            logger.trace("Did not match converter " + conditional);
                        }
                    }
                }
            }
            if (this.defaultConverter != null && logger.isTraceEnabled()) {
                logger.trace("Matched converter " + this.defaultConverter);
            }
            return this.defaultConverter;
        }

        public String toString() {
            if (this.conditionalConverters != null) {
                StringBuilder builder = new StringBuilder();
                for (Iterator<ConditionalGenericConverter> it = this.conditionalConverters.iterator(); it
                        .hasNext();) {
                    builder.append(it.next());
                    if (it.hasNext()) {
                        builder.append(", ");
                    }
                }
                if (this.defaultConverter != null) {
                    builder.append(", ").append(this.defaultConverter);
                }
                return builder.toString();
            } else {
                return this.defaultConverter.toString();
            }
        }
    }

    private static final class ConverterCacheKey {

        private final TypeDescriptor sourceType;

        private final TypeDescriptor targetType;

        public ConverterCacheKey(TypeDescriptor sourceType, TypeDescriptor targetType) {
            this.sourceType = sourceType;
            this.targetType = targetType;
        }

        public boolean equals(Object other) {
            if (this == other) {
                return true;
            }
            if (!(other instanceof ConverterCacheKey)) {
                return false;
            }
            ConverterCacheKey otherKey = (ConverterCacheKey) other;
            return this.sourceType.equals(otherKey.sourceType) && this.targetType.equals(otherKey.targetType);
        }

        public int hashCode() {
            return this.sourceType.hashCode() * 29 + this.targetType.hashCode();
        }

        public String toString() {
            return "ConverterCacheKey [sourceType = " + this.sourceType + ", targetType = " + this.targetType + "]";
        }
    }

}