Package org.springframework.core.convert.support

Source Code of org.springframework.core.convert.support.GenericConversionService$ConverterFactoryAdapter

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

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.springframework.core.ResolvableType;
import org.springframework.core.convert.ConversionException;
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.ConditionalConverter;
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.convert.converter.GenericConverter.ConvertiblePair;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

/**
* Base {@link ConversionService} implementation suitable for use in most environments.
* Indirectly implements {@link ConverterRegistry} as registration API through the
* {@link ConfigurableConversionService} interface.
*
* @author Keith Donald
* @author Juergen Hoeller
* @author Chris Beams
* @author Phillip Webb
* @author David Haraburda
* @since 3.0
*/
public class GenericConversionService implements ConfigurableConversionService {

  /**
   * General NO-OP converter used when conversion is not required.
   */
  private static final GenericConverter NO_OP_CONVERTER = new NoOpConverter("NO_OP");

  /**
   * Used as a cache entry when no converter is available.  This converter is never
   * returned.
   */
  private static final GenericConverter NO_MATCH = new NoOpConverter("NO_MATCH");


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

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


  private final Converters converters = new Converters();

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


  // implementing ConverterRegistry

  @Override
  public void addConverter(Converter<?, ?> converter) {
    ResolvableType[] typeInfo = getRequiredTypeInfo(converter, Converter.class);
    Assert.notNull(typeInfo, "Unable to the determine sourceType <S> and targetType " +
        "<T> which your Converter<S, T> converts between; declare these generic types.");
    addConverter(new ConverterAdapter(converter, typeInfo[0], typeInfo[1]));
  }

  @Override
  public void addConverter(Class<?> sourceType, Class<?> targetType, Converter<?, ?> converter) {
    addConverter(new ConverterAdapter(converter, ResolvableType.forClass(sourceType), ResolvableType.forClass(targetType)));
  }

  @Override
  public void addConverter(GenericConverter converter) {
    this.converters.add(converter);
    invalidateCache();
  }

  @Override
  public void addConverterFactory(ConverterFactory<?, ?> converterFactory) {
    ResolvableType[] typeInfo = getRequiredTypeInfo(converterFactory, ConverterFactory.class);
    Assert.notNull("Unable to the determine sourceType <S> and targetRangeType R which your " +
        "ConverterFactory<S, R> converts between; declare these generic types.");
    addConverter(new ConverterFactoryAdapter(converterFactory,
        new ConvertiblePair(typeInfo[0].resolve(), typeInfo[1].resolve())));
  }

  @Override
  public void removeConvertible(Class<?> sourceType, Class<?> targetType) {
    this.converters.remove(sourceType, targetType);
    invalidateCache();
  }

  // implementing ConversionService

  @Override
  public boolean canConvert(Class<?> sourceType, Class<?> targetType) {
    Assert.notNull(targetType, "targetType to convert to cannot be null");
    return canConvert((sourceType != null ? TypeDescriptor.valueOf(sourceType) : null),
        TypeDescriptor.valueOf(targetType));
  }

  @Override
  public boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType) {
    Assert.notNull(targetType, "targetType to convert to cannot be null");
    if (sourceType == null) {
      return true;
    }
    GenericConverter converter = getConverter(sourceType, targetType);
    return (converter != null);
  }

  /**
   * Returns true if conversion between the sourceType and targetType can be bypassed.
   * More precisely this method will return true if objects of sourceType can be
   * converted to the targetType by returning the source object unchanged.
   * @param sourceType context about the source type to convert from (may be null if source is null)
   * @param targetType context about the target type to convert to (required)
   * @return true if conversion can be bypassed
   * @throws IllegalArgumentException if targetType is null
   * @since 3.2
   */
  public boolean canBypassConvert(TypeDescriptor sourceType, TypeDescriptor targetType) {
    Assert.notNull(targetType, "The targetType to convert to cannot be null");
    if (sourceType == null) {
      return true;
    }
    GenericConverter converter = getConverter(sourceType, targetType);
    return (converter == NO_OP_CONVERTER);
  }

  @Override
  @SuppressWarnings("unchecked")
  public <T> T convert(Object source, Class<T> targetType) {
    Assert.notNull(targetType,"The targetType to convert to cannot be null");
    return (T) convert(source, TypeDescriptor.forObject(source), TypeDescriptor.valueOf(targetType));
  }

  @Override
  public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
    Assert.notNull(targetType,"The targetType to convert to cannot be null");
    if (sourceType == null) {
      Assert.isTrue(source == null, "The source must be [null] if sourceType == [null]");
      return handleResult(sourceType, targetType, convertNullSource(sourceType, targetType));
    }
    if (source != null && !sourceType.getObjectType().isInstance(source)) {
      throw new IllegalArgumentException("The source to convert from must be an instance of " +
          sourceType + "; instead it was a " + source.getClass().getName());
    }
    GenericConverter converter = getConverter(sourceType, targetType);
    if (converter != null) {
      Object result = ConversionUtils.invokeConverter(converter, source, sourceType, targetType);
      return handleResult(sourceType, targetType, result);
    }
    return handleConverterNotFound(source, sourceType, targetType);
  }

  /**
   * Convenience operation for converting a source object to the specified targetType,
   * where the targetType is a descriptor that provides additional conversion context.
   * Simply delegates to {@link #convert(Object, TypeDescriptor, TypeDescriptor)} and
   * encapsulates the construction of the sourceType descriptor using
   * {@link TypeDescriptor#forObject(Object)}.
   * @param source the source object
   * @param targetType the target type
   * @return the converted value
   * @throws ConversionException if a conversion exception occurred
   * @throws IllegalArgumentException if targetType is null,
   * or sourceType is null but source is not null
   */
  public Object convert(Object source, TypeDescriptor targetType) {
    return convert(source, TypeDescriptor.forObject(source), targetType);
  }

  @Override
  public String toString() {
    return this.converters.toString();
  }


  // Protected template methods

  /**
   * Template method to convert a null source.
   * <p>Default implementation returns {@code null} or the Java 8
   * {@link java.util.Optional#empty()} instance if the target type is
   * {@code java.uti.Optional}.
   * 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 (javaUtilOptionalEmpty != null && targetType.getObjectType().equals(javaUtilOptionalEmpty.getClass())) {
      return javaUtilOptionalEmpty;
    }
    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} 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) {
      return (converter != NO_MATCH ? converter : null);
    }

    converter = this.converters.find(sourceType, targetType);
    if (converter == null) {
      converter = getDefaultConverter(sourceType, targetType);
    }

    if (converter != null) {
      this.converterCache.put(key, converter);
      return converter;
    }

    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} 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) {
    return (sourceType.isAssignableTo(targetType) ? NO_OP_CONVERTER : null);
  }

  // internal helpers

  private ResolvableType[] getRequiredTypeInfo(Object converter, Class<?> genericIfc) {
    ResolvableType resolvableType = ResolvableType.forClass(converter.getClass()).as(genericIfc);
    ResolvableType[] generics = resolvableType.getGenerics();
    if (generics.length < 2) {
      return null;
    }
    Class<?> sourceType = generics[0].resolve();
    Class<?> targetType = generics[1].resolve();
    if (sourceType == null || targetType == null) {
      return null;
    }
    return generics;
  }

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

  private Object handleConverterNotFound(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
    if (source == null) {
      assertNotPrimitiveTargetType(sourceType, targetType);
      return source;
    }
    if (sourceType.isAssignableTo(targetType) && targetType.getObjectType().isInstance(source)) {
      return source;
    }
    throw new ConverterNotFoundException(sourceType, targetType);
  }

  private Object handleResult(TypeDescriptor sourceType, TypeDescriptor targetType, Object result) {
    if (result == null) {
      assertNotPrimitiveTargetType(sourceType, targetType);
    }
    return result;
  }

  private void assertNotPrimitiveTargetType(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"));
    }
  }


  /**
   * Adapts a {@link Converter} to a {@link GenericConverter}.
   */
  @SuppressWarnings("unchecked")
  private final class ConverterAdapter implements ConditionalGenericConverter {

    private final Converter<Object, Object> converter;

    private final ConvertiblePair typeInfo;

    private final ResolvableType targetType;

    public ConverterAdapter(Converter<?, ?> converter, ResolvableType sourceType, ResolvableType targetType) {
      this.converter = (Converter<Object, Object>) converter;
      this.typeInfo = new ConvertiblePair(sourceType.resolve(Object.class), targetType.resolve(Object.class));
      this.targetType = targetType;
    }

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

    @Override
    public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
      // Check raw type first...
      if (!this.typeInfo.getTargetType().equals(targetType.getObjectType())) {
        return false;
      }
      // Full check for complex generic type match required?
      ResolvableType rt = targetType.getResolvableType();
      if (!(rt.getType() instanceof Class) && !rt.isAssignableFrom(this.targetType) &&
          !this.targetType.hasUnresolvableGenerics()) {
        return false;
      }
      return !(this.converter instanceof ConditionalConverter) ||
          ((ConditionalConverter) this.converter).matches(sourceType, targetType);
    }

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

    @Override
    public String toString() {
      return this.typeInfo + " : " + this.converter;
    }
  }


  /**
   * Adapts a {@link ConverterFactory} to a {@link GenericConverter}.
   */
  @SuppressWarnings("unchecked")
  private final class ConverterFactoryAdapter implements ConditionalGenericConverter {

    private final ConverterFactory<Object, Object> converterFactory;

    private final ConvertiblePair typeInfo;

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

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

    @Override
    public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
      boolean matches = true;
      if (this.converterFactory instanceof ConditionalConverter) {
        matches = ((ConditionalConverter) this.converterFactory).matches(sourceType, targetType);
      }
      if (matches) {
        Converter<?, ?> converter = this.converterFactory.getConverter(targetType.getType());
        if (converter instanceof ConditionalConverter) {
          matches = ((ConditionalConverter) converter).matches(sourceType, targetType);
        }
      }
      return matches;
    }

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

    @Override
    public String toString() {
      return this.typeInfo + " : " + this.converterFactory;
    }
  }


  /**
   * Key for use with the converter cache.
   */
  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;
    }

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

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

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


  /**
   * Manages all converters registered with the service.
   */
  private static class Converters {

    private final Set<GenericConverter> globalConverters = new LinkedHashSet<GenericConverter>();

    private final Map<ConvertiblePair, ConvertersForPair> converters =
        new LinkedHashMap<ConvertiblePair, ConvertersForPair>(36);

    public void add(GenericConverter converter) {
      Set<ConvertiblePair> convertibleTypes = converter.getConvertibleTypes();
      if (convertibleTypes == null) {
        Assert.state(converter instanceof ConditionalConverter,
            "Only conditional converters may return null convertible types");
        this.globalConverters.add(converter);
      }
      else {
        for (ConvertiblePair convertiblePair : convertibleTypes) {
          ConvertersForPair convertersForPair = getMatchableConverters(convertiblePair);
          convertersForPair.add(converter);
        }
      }
    }

    private ConvertersForPair getMatchableConverters(ConvertiblePair convertiblePair) {
      ConvertersForPair convertersForPair = this.converters.get(convertiblePair);
      if (convertersForPair == null) {
        convertersForPair = new ConvertersForPair();
        this.converters.put(convertiblePair, convertersForPair);
      }
      return convertersForPair;
    }

    public void remove(Class<?> sourceType, Class<?> targetType) {
      this.converters.remove(new ConvertiblePair(sourceType, targetType));
    }

    /**
     * Find a {@link GenericConverter} given a source and target type.
     * <p>This method will attempt to match all possible converters by working
     * through the class and interface hierarchy of the types.
     * @param sourceType the source type
     * @param targetType the target type
     * @return a matching {@link GenericConverter}, or {@code null} if none found
     */
    public GenericConverter find(TypeDescriptor sourceType, TypeDescriptor targetType) {
      // Search the full type hierarchy
      List<Class<?>> sourceCandidates = getClassHierarchy(sourceType.getType());
      List<Class<?>> targetCandidates = getClassHierarchy(targetType.getType());
      for (Class<?> sourceCandidate : sourceCandidates) {
        for (Class<?> targetCandidate : targetCandidates) {
          ConvertiblePair convertiblePair = new ConvertiblePair(sourceCandidate, targetCandidate);
          GenericConverter converter = getRegisteredConverter(sourceType, targetType, convertiblePair);
          if (converter != null) {
            return converter;
          }
        }
      }
      return null;
    }

    private GenericConverter getRegisteredConverter(TypeDescriptor sourceType,
        TypeDescriptor targetType, ConvertiblePair convertiblePair) {

      // Check specifically registered converters
      ConvertersForPair convertersForPair = this.converters.get(convertiblePair);
      if (convertersForPair != null) {
        GenericConverter converter = convertersForPair.getConverter(sourceType, targetType);
        if (converter != null) {
          return converter;
        }
      }
      // Check ConditionalGenericConverter that match all types
      for (GenericConverter globalConverter : this.globalConverters) {
        if (((ConditionalConverter)globalConverter).matches(sourceType, targetType)) {
          return globalConverter;
        }
      }
      return null;
    }

    /**
     * Returns an ordered class hierarchy for the given type.
     * @param type the type
     * @return an ordered list of all classes that the given type extends or implements
     */
    private List<Class<?>> getClassHierarchy(Class<?> type) {
      List<Class<?>> hierarchy = new ArrayList<Class<?>>(20);
      Set<Class<?>> visited = new HashSet<Class<?>>(20);
      addToClassHierarchy(0, ClassUtils.resolvePrimitiveIfNecessary(type), false, hierarchy, visited);
      boolean array = type.isArray();
      int i = 0;
      while (i < hierarchy.size()) {
        Class<?> candidate = hierarchy.get(i);
        candidate = (array ? candidate.getComponentType() : ClassUtils.resolvePrimitiveIfNecessary(candidate));
        Class<?> superclass = candidate.getSuperclass();
        if (superclass != null && superclass != Object.class && superclass != Enum.class) {
          addToClassHierarchy(i + 1, candidate.getSuperclass(), array, hierarchy, visited);
        }
        addInterfacesToClassHierarchy(candidate, array, hierarchy, visited);
        i++;
      }

      if (Enum.class.isAssignableFrom(type)) {
        addToClassHierarchy(hierarchy.size(), Enum.class, array, hierarchy, visited);
        addToClassHierarchy(hierarchy.size(), Enum.class, false, hierarchy, visited);
        addInterfacesToClassHierarchy(Enum.class, array, hierarchy, visited);
      }

      addToClassHierarchy(hierarchy.size(), Object.class, array, hierarchy, visited);
      addToClassHierarchy(hierarchy.size(), Object.class, false, hierarchy, visited);
      return hierarchy;
    }

    private void addInterfacesToClassHierarchy(Class<?> type, boolean asArray,
        List<Class<?>> hierarchy, Set<Class<?>> visited) {

      for (Class<?> implementedInterface : type.getInterfaces()) {
        addToClassHierarchy(hierarchy.size(), implementedInterface, asArray, hierarchy, visited);
      }
    }

    private void addToClassHierarchy(int index, Class<?> type, boolean asArray,
        List<Class<?>> hierarchy, Set<Class<?>> visited) {

      if (asArray) {
        type = Array.newInstance(type, 0).getClass();
      }
      if (visited.add(type)) {
        hierarchy.add(index, type);
      }
    }

    @Override
    public String toString() {
      StringBuilder builder = new StringBuilder();
      builder.append("ConversionService converters =\n");
      for (String converterString : getConverterStrings()) {
        builder.append('\t').append(converterString).append('\n');
      }
      return builder.toString();
    }

    private List<String> getConverterStrings() {
      List<String> converterStrings = new ArrayList<String>();
      for (ConvertersForPair convertersForPair : converters.values()) {
        converterStrings.add(convertersForPair.toString());
      }
      Collections.sort(converterStrings);
      return converterStrings;
    }
  }


  /**
   * Manages converters registered with a specific {@link ConvertiblePair}.
   */
  private static class ConvertersForPair {

    private final LinkedList<GenericConverter> converters = new LinkedList<GenericConverter>();

    public void add(GenericConverter converter) {
      this.converters.addFirst(converter);
    }

    public GenericConverter getConverter(TypeDescriptor sourceType, TypeDescriptor targetType) {
      for (GenericConverter converter : this.converters) {
        if (!(converter instanceof ConditionalGenericConverter) ||
            ((ConditionalGenericConverter) converter).matches(sourceType, targetType)) {
          return converter;
        }
      }
      return null;
    }

    @Override
    public String toString() {
      return StringUtils.collectionToCommaDelimitedString(this.converters);
    }
  }


  /**
   * Internal converter that performs no operation.
   */
  private static class NoOpConverter implements GenericConverter {

    private final String name;

    public NoOpConverter(String name) {
      this.name = name;
    }

    @Override
    public Set<ConvertiblePair> getConvertibleTypes() {
      return null;
    }

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

    @Override
    public String toString() {
      return this.name;
    }
  }

}
TOP

Related Classes of org.springframework.core.convert.support.GenericConversionService$ConverterFactoryAdapter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.