/* * Copyright 2004-2012 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.binding.convert.service; import java.lang.reflect.Modifier; import java.util.Collection; import java.util.HashMap; import java.util.Map; import org.springframework.binding.convert.ConversionException; import org.springframework.binding.convert.ConversionExecutor; import org.springframework.binding.convert.ConversionExecutorNotFoundException; import org.springframework.binding.convert.ConversionService; import org.springframework.binding.convert.converters.ArrayToArray; import org.springframework.binding.convert.converters.ArrayToCollection; import org.springframework.binding.convert.converters.CollectionToCollection; import org.springframework.binding.convert.converters.Converter; import org.springframework.binding.convert.converters.ObjectToArray; import org.springframework.binding.convert.converters.ObjectToCollection; import org.springframework.binding.convert.converters.ReverseConverter; import org.springframework.binding.convert.converters.SpringConvertingConverterAdapter; import org.springframework.binding.convert.converters.TwoWayConverter; import org.springframework.core.convert.converter.ConverterRegistry; import org.springframework.format.support.FormattingConversionServiceFactoryBean; import org.springframework.util.Assert; /** * Base implementation of a conversion service. Initially empty, e.g. no converters are registered by default. * * @author Keith Donald */ public class GenericConversionService implements ConversionService { /** * Spring ConversionService where existing custom {@link Converter} types will be registered through an adapter. */ private org.springframework.core.convert.ConversionService delegate; /** * A map of custom converters. Custom converters are assigned a unique identifier that can be used to lookup the * converter. This allows multiple converters for the same source->target class to be registered. */ private final Map<String, Converter> customConverters = new HashMap<String, Converter>(); /** * Indexes classes by well-known aliases. */ private final Map<String, Class<?>> aliasMap = new HashMap<String, Class<?>>(); /** * An optional parent conversion service. */ private ConversionService parent; /** * Default constructor. */ public GenericConversionService() { FormattingConversionServiceFactoryBean factoryBean = new FormattingConversionServiceFactoryBean(); factoryBean.afterPropertiesSet(); this.delegate = factoryBean.getObject(); } /** * Constructor accepting a specific instance of a Spring ConversionService to delegate to. * @param delegateConversionService the conversion service */ public GenericConversionService(org.springframework.core.convert.ConversionService delegateConversionService) { Assert.notNull(delegateConversionService); this.delegate = delegateConversionService; } /** * Returns the parent of this conversion service. Could be null. */ public ConversionService getParent() { return parent; } /** * Set the parent of this conversion service. This is optional. */ public void setParent(ConversionService parent) { this.parent = parent; } /** * @return the Spring ConverterRegistry */ public org.springframework.core.convert.ConversionService getDelegateConversionService() { return delegate; } /** * Registers the given converter with the underlying Spring ConversionService with the help of an adapter. The * adapter allows an existing Spring Binding converter to be invoked within Spring's type conversion system. * * @param converter the converter * * @see ConverterRegistry * @see org.springframework.core.convert.ConversionService * @see SpringBindingConverterAdapter */ public void addConverter(Converter converter) { ((ConverterRegistry) delegate).addConverter(new SpringBindingConverterAdapter(converter)); if (converter instanceof TwoWayConverter) { TwoWayConverter twoWayConverter = (TwoWayConverter) converter; ((ConverterRegistry) delegate).addConverter(new SpringBindingConverterAdapter(new ReverseConverter( twoWayConverter))); } } /** * Add given custom converter to this conversion service. * * Note: Converters registered through this method will not be involve the Spring type conversion system, which is * now used the default type conversion mechanism. Spring's type conversion does not support named converters. This * method is provided for backwards compatibility. * * @param id the id of the custom converter instance * @param converter the converter * * @deprecated use {@link #addConverter(Converter)} instead or better yet use Spring 3 type conversion and * formatting options (see Spring Documentation). */ public void addConverter(String id, Converter converter) { customConverters.put(id, converter); } /** * Add an alias for given target type. */ public void addAlias(String alias, Class<?> targetType) { aliasMap.put(alias, targetType); } public ConversionExecutor getConversionExecutor(Class<?> sourceClass, Class<?> targetClass) throws ConversionExecutorNotFoundException { Assert.notNull(sourceClass, "The source class to convert from is required"); Assert.notNull(targetClass, "The target class to convert to is required"); sourceClass = convertToWrapperClassIfNecessary(sourceClass); targetClass = convertToWrapperClassIfNecessary(targetClass); if (targetClass.isAssignableFrom(sourceClass)) { return new StaticConversionExecutor(sourceClass, targetClass, new NoOpConverter(sourceClass, targetClass)); } if (delegate.canConvert(sourceClass, targetClass)) { return new StaticConversionExecutor(sourceClass, targetClass, new SpringConvertingConverterAdapter( sourceClass, targetClass, delegate)); } else if (parent != null) { return parent.getConversionExecutor(sourceClass, targetClass); } else { throw new ConversionExecutorNotFoundException(sourceClass, targetClass, "No ConversionExecutor found for converting from sourceClass [" + sourceClass.getName() + "] to target class [" + targetClass.getName() + "]"); } } public ConversionExecutor getConversionExecutor(String id, Class<?> sourceClass, Class<?> targetClass) throws ConversionExecutorNotFoundException { Assert.hasText(id, "The id of the custom converter is required"); Assert.notNull(sourceClass, "The source class to convert from is required"); Assert.notNull(targetClass, "The target class to convert to is required"); Converter converter = customConverters.get(id); if (converter == null) { if (parent != null) { return parent.getConversionExecutor(id, sourceClass, targetClass); } else { throw new ConversionExecutorNotFoundException(sourceClass, targetClass, "No custom ConversionExecutor found with id '" + id + "' for converting from sourceClass [" + sourceClass.getName() + "] to targetClass [" + targetClass.getName() + "]"); } } sourceClass = convertToWrapperClassIfNecessary(sourceClass); targetClass = convertToWrapperClassIfNecessary(targetClass); if (sourceClass.isArray()) { Class<?> sourceComponentType = sourceClass.getComponentType(); if (targetClass.isArray()) { Class<?> targetComponentType = targetClass.getComponentType(); if (converter.getSourceClass().isAssignableFrom(sourceComponentType)) { if (!converter.getTargetClass().isAssignableFrom(targetComponentType)) { throw new ConversionExecutorNotFoundException(sourceClass, targetClass, "Custom ConversionExecutor with id '" + id + "' cannot convert from an array storing elements of type [" + sourceComponentType.getName() + "] to an array of storing elements of type [" + targetComponentType.getName() + "]"); } ConversionExecutor elementConverter = new StaticConversionExecutor(sourceComponentType, targetComponentType, converter); return new StaticConversionExecutor(sourceClass, targetClass, new ArrayToArray(elementConverter)); } else if (converter.getTargetClass().isAssignableFrom(sourceComponentType) && converter instanceof TwoWayConverter) { TwoWayConverter twoWay = (TwoWayConverter) converter; ConversionExecutor elementConverter = new StaticConversionExecutor(sourceComponentType, targetComponentType, new ReverseConverter(twoWay)); return new StaticConversionExecutor(sourceClass, targetClass, new ArrayToArray(elementConverter)); } else { throw new ConversionExecutorNotFoundException(sourceClass, targetClass, "Custom ConversionExecutor with id '" + id + "' cannot convert from an array storing elements of type [" + sourceComponentType.getName() + "] to an array storing elements of type [" + targetComponentType.getName() + "]"); } } else if (Collection.class.isAssignableFrom(targetClass)) { if (!targetClass.isInterface() && Modifier.isAbstract(targetClass.getModifiers())) { throw new IllegalArgumentException("Conversion target class [" + targetClass.getName() + "] is invalid; cannot convert to abstract collection types--" + "request an interface or concrete implementation instead"); } if (converter.getSourceClass().isAssignableFrom(sourceComponentType)) { // type erasure has prevented us from getting the concrete type, this is best we can do for now ConversionExecutor elementConverter = new StaticConversionExecutor(sourceComponentType, converter.getTargetClass(), converter); return new StaticConversionExecutor(sourceClass, targetClass, new ArrayToCollection( elementConverter)); } else if (converter.getTargetClass().isAssignableFrom(sourceComponentType) && converter instanceof TwoWayConverter) { TwoWayConverter twoWay = (TwoWayConverter) converter; ConversionExecutor elementConverter = new StaticConversionExecutor(sourceComponentType, converter.getSourceClass(), new ReverseConverter(twoWay)); return new StaticConversionExecutor(sourceClass, targetClass, new ArrayToCollection( elementConverter)); } else { throw new ConversionExecutorNotFoundException(sourceClass, targetClass, "Custom ConversionExecutor with id '" + id + "' cannot convert from array an storing elements type [" + sourceComponentType.getName() + "] to a collection of type [" + targetClass.getName() + "]"); } } } if (targetClass.isArray()) { Class<?> targetComponentType = targetClass.getComponentType(); if (Collection.class.isAssignableFrom(sourceClass)) { // type erasure limits us here as well if (converter.getTargetClass().isAssignableFrom(targetComponentType)) { ConversionExecutor elementConverter = new StaticConversionExecutor(converter.getSourceClass(), targetComponentType, converter); Converter collectionToArray = new ReverseConverter(new ArrayToCollection(elementConverter)); return new StaticConversionExecutor(sourceClass, targetClass, collectionToArray); } else if (converter.getSourceClass().isAssignableFrom(targetComponentType) && converter instanceof TwoWayConverter) { TwoWayConverter twoWay = (TwoWayConverter) converter; ConversionExecutor elementConverter = new StaticConversionExecutor(converter.getTargetClass(), targetComponentType, new ReverseConverter(twoWay)); Converter collectionToArray = new ReverseConverter(new ArrayToCollection(elementConverter)); return new StaticConversionExecutor(sourceClass, targetClass, collectionToArray); } else { throw new ConversionExecutorNotFoundException(sourceClass, targetClass, "Custom ConversionExecutor with id '" + id + "' cannot convert from collection of type [" + sourceClass.getName() + "] to an array storing elements of type [" + targetComponentType.getName() + "]"); } } else { if (converter.getSourceClass().isAssignableFrom(sourceClass)) { if (!converter.getTargetClass().isAssignableFrom(targetComponentType)) { throw new ConversionExecutorNotFoundException(sourceClass, targetClass, "Custom ConversionExecutor with id '" + id + "' cannot convert from sourceClass [" + sourceClass.getName() + "] to array holding elements of type [" + targetComponentType.getName() + "]"); } ConversionExecutor elementConverter = new StaticConversionExecutor(sourceClass, targetComponentType, converter); return new StaticConversionExecutor(sourceClass, targetClass, new ObjectToArray(elementConverter)); } else if (converter.getTargetClass().isAssignableFrom(sourceClass) && converter instanceof TwoWayConverter) { if (!converter.getSourceClass().isAssignableFrom(targetComponentType)) { throw new ConversionExecutorNotFoundException(sourceClass, targetClass, "Custom ConversionExecutor with id '" + id + "' cannot convert from sourceClass [" + sourceClass.getName() + "] to array holding elements of type [" + targetComponentType.getName() + "]"); } TwoWayConverter twoWay = (TwoWayConverter) converter; ConversionExecutor elementConverter = new StaticConversionExecutor(sourceClass, targetComponentType, new ReverseConverter(twoWay)); return new StaticConversionExecutor(sourceClass, targetClass, new ObjectToArray(elementConverter)); } } } if (Collection.class.isAssignableFrom(targetClass)) { if (Collection.class.isAssignableFrom(sourceClass)) { ConversionExecutor elementConverter; // type erasure forces us to do runtime checks of list elements if (converter instanceof TwoWayConverter) { elementConverter = new TwoWayCapableConversionExecutor(converter.getSourceClass(), converter.getTargetClass(), (TwoWayConverter) converter); } else { elementConverter = new StaticConversionExecutor(converter.getSourceClass(), converter.getTargetClass(), converter); } return new StaticConversionExecutor(sourceClass, targetClass, new CollectionToCollection( elementConverter)); } else { ConversionExecutor elementConverter; // type erasure forces us to do runtime checks of list elements if (converter instanceof TwoWayConverter) { elementConverter = new TwoWayCapableConversionExecutor(sourceClass, converter.getTargetClass(), (TwoWayConverter) converter); } else { elementConverter = new StaticConversionExecutor(sourceClass, converter.getTargetClass(), converter); } if (!Collection.class.isAssignableFrom(converter.getTargetClass())) { elementConverter = new StaticConversionExecutor(sourceClass, targetClass, new ObjectToCollection( elementConverter)); } return elementConverter; } } if (converter.getSourceClass().isAssignableFrom(sourceClass)) { if (!converter.getTargetClass().isAssignableFrom(targetClass)) { throw new ConversionExecutorNotFoundException(sourceClass, targetClass, "Custom ConversionExecutor with id '" + id + "' cannot convert from sourceClass [" + sourceClass.getName() + "] to targetClass [" + targetClass.getName() + "]"); } return new StaticConversionExecutor(sourceClass, targetClass, converter); } else if (converter.getTargetClass().isAssignableFrom(sourceClass) && converter instanceof TwoWayConverter) { if (!converter.getSourceClass().isAssignableFrom(targetClass)) { throw new ConversionExecutorNotFoundException(sourceClass, targetClass, "Custom ConversionExecutor with id '" + id + "' cannot convert from sourceClass [" + sourceClass.getName() + "] to targetClass [" + targetClass.getName() + "]"); } TwoWayConverter twoWay = (TwoWayConverter) converter; return new StaticConversionExecutor(sourceClass, targetClass, new ReverseConverter(twoWay)); } else { throw new ConversionExecutorNotFoundException(sourceClass, targetClass, "Custom ConversionExecutor with id '" + id + "' cannot convert from sourceClass [" + sourceClass.getName() + "] to targetClass [" + targetClass.getName() + "]"); } } public Object executeConversion(Object source, Class<?> targetClass) throws ConversionException { if (source != null) { ConversionExecutor conversionExecutor = getConversionExecutor(source.getClass(), targetClass); return conversionExecutor.execute(source); } else { return null; } } public Object executeConversion(String converterId, Object source, Class<?> targetClass) throws ConversionException { if (source != null) { ConversionExecutor conversionExecutor = getConversionExecutor(converterId, source.getClass(), targetClass); return conversionExecutor.execute(source); } else { return null; } } public Class<?> getClassForAlias(String name) throws IllegalArgumentException { Class<?> clazz = aliasMap.get(name); if (clazz != null) { return clazz; } else { if (parent != null) { return parent.getClassForAlias(name); } else { return null; } } } // internal helpers private Class<?> convertToWrapperClassIfNecessary(Class<?> targetType) { if (targetType.isPrimitive()) { if (targetType.equals(int.class)) { return Integer.class; } else if (targetType.equals(short.class)) { return Short.class; } else if (targetType.equals(long.class)) { return Long.class; } else if (targetType.equals(float.class)) { return Float.class; } else if (targetType.equals(double.class)) { return Double.class; } else if (targetType.equals(byte.class)) { return Byte.class; } else if (targetType.equals(boolean.class)) { return Boolean.class; } else if (targetType.equals(char.class)) { return Character.class; } else { throw new IllegalStateException("Should never happen - primitive type is not a primitive?"); } } else { return targetType; } } }