Open Source Repository

Home /spring/spring-core-3.0.5 | Repository Home


org/springframework/core/convert/support/GenericConversionService.java
/*
 * Copyright 2002-2010 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 (Tconvert(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 || targetType.getObjectType().isInstance(source)) {
        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((ConditionalGenericConverterconverter);
      }
      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 = (ConverterCacheKeyother;
      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 + "]";
    }
  }

}