Converters.java

package com.moandjiezana.uncommons.dbutils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.sql.ResultSet;
import java.sql.Timestamp;
import java.time.Instant;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

/**
 * Groups {@link Converter}s together.
 * 
 * {@link Converters#INSTANCE} is a singleton that provides defaults and makes registered {@link Converter}s available globally
 *
 */
public interface Converters {
  
  /**
   * @param targetClass
   *    the class to convert value to
   * @param value
   *    the object to be converted
   * @param <T>
   *    the type of the converted value
   * @return an instance of T
   */
  <T> T convert(Class<T> targetClass, Object value);
  
  /**
   * @param targetClass
   *    the type handled by this {@link Converter}
   * @param converter
   *    the {@link Converter}
   * @param <T>
   *    the type of the converted value
   */
  <T> void register(Class<T> targetClass, Converter<T> converter);
  
  /**
   * A singleton that provides defaults and makes registered {@link Converter}s available globally.
   * 
   * Supports all types returned by {@link ResultSet}#getXxx() methods, {@link Instant} and any class with a static <code>valueOf(String)</code> method.
   * 
   * If no conversion can be performed, returns <code>null</code>.
   */
  static final Converters INSTANCE = new Converters() {
    private final Map<Class<?>, Converter<?>> converters = new HashMap<>();
    
    {
      register(Instant.class, (cl, value) -> ((Timestamp) value).toInstant());
    }

    @Override
    public <T> T convert(Class<T> targetClass, Object value) {
      if (value == null) {
        return null;
      }
      
      if (targetClass.isAssignableFrom(value.getClass())) {
        return targetClass.cast(value);
      }
      
      Optional<Method> valueOfMethod = Arrays.stream(targetClass.getMethods())
        .filter(m -> Modifier.isStatic(m.getModifiers()))
        .filter(m -> m.getName().equals("valueOf"))
        .filter(m -> m.getParameterCount() == 1 && m.getParameterTypes()[0] == value.getClass())
        .findFirst();
      
      if (valueOfMethod.isPresent()) {
        try {
          return targetClass.cast(valueOfMethod.get().invoke(null, value));
        } catch (IllegalAccessException | InvocationTargetException e) {
          throw new RuntimeException(e);
        }
      }
      
      if (!converters.containsKey(targetClass)) {
        throw new IllegalArgumentException("Cannot convert to " + targetClass.getName());
      }
      
      @SuppressWarnings("unchecked")
      Converter<T> converter = (Converter<T>) converters.get(targetClass);
      return converter.convert(targetClass, value);
    }
    
    @Override
    public <T> void register(Class<T> targetClass, Converter<T> converter) {
      converters.put(targetClass, converter);
    }
  };
}