package nl.ipo.cds.attributemapping; import java.lang.reflect.GenericArrayType; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.HashMap; import java.util.Map; import com.googlecode.gentyref.GenericTypeReflector; public final class AttributeMapperUtils { private final static Map<Class<?>, Class<?>> primitiveToBoxed = new HashMap<Class<?>, Class<?>> (); private final static Map<Class<?>, Class<?>> boxedToPrimitive = new HashMap<Class<?>, Class<?>> (); private final static Map<Class<?>, Integer> integerRank = new HashMap<Class<?>, Integer> (); private final static Map<Class<?>, Integer> floatRank = new HashMap<Class<?>, Integer> (); static { primitiveToBoxed.put (Byte.TYPE, Byte.class); primitiveToBoxed.put (Short.TYPE, Short.class); primitiveToBoxed.put (Integer.TYPE, Integer.class); primitiveToBoxed.put (Long.TYPE, Long.class); primitiveToBoxed.put (Float.TYPE, Float.class); primitiveToBoxed.put (Double.TYPE, Double.class); primitiveToBoxed.put (Boolean.TYPE, Boolean.class); primitiveToBoxed.put (Character.TYPE, Character.class); boxedToPrimitive.put (Byte.class, Byte.TYPE); boxedToPrimitive.put (Short.class, Short.TYPE); boxedToPrimitive.put (Integer.class, Integer.TYPE); boxedToPrimitive.put (Long.class, Long.TYPE); boxedToPrimitive.put (Float.class, Float.TYPE); boxedToPrimitive.put (Double.class, Double.TYPE); boxedToPrimitive.put (Boolean.class, Boolean.TYPE); boxedToPrimitive.put (Character.class, Character.TYPE); integerRank.put (Byte.TYPE, 0); integerRank.put (Character.TYPE, 1); integerRank.put (Short.TYPE, 1); integerRank.put (Integer.TYPE, 2); integerRank.put (Long.TYPE, 3); floatRank.put (Float.TYPE, 0); floatRank.put (Double.TYPE, 1); } public static boolean areTypesAssignable (final Type sourceType, final Type destinationType) { if (sourceType instanceof Class<?> && destinationType instanceof Class<?>) { final Class<?> sourceClass = (Class<?>)sourceType; final Class<?> destinationClass = (Class<?>)destinationType; // NullReference can be assigned to any if (sourceClass.equals (NullReference.class)) { return true; } // Classes are only assignable if the destination is not generic: return destinationClass.getTypeParameters ().length == 0 && (!destinationClass.isArray () || getArrayComponentType (destinationClass).getTypeParameters ().length == 0) && areClassesAssignable (sourceClass, destinationClass); } else if (destinationType instanceof ParameterizedType){ return areGenericTypesAssignable (sourceType, (ParameterizedType)destinationType); } else if (destinationType instanceof GenericArrayType) { return areGenericArraysAssignable (sourceType, (GenericArrayType)destinationType); } return false; } public static Class<?> getArrayComponentType (Class<?> cls) { while (cls.isArray ()) { cls = cls.getComponentType (); } return cls; } public static boolean areGenericArraysAssignable (final Type sourceType, final GenericArrayType destinationType) { if (!areClassesAssignable (GenericTypeReflector.erase (sourceType), GenericTypeReflector.erase (destinationType))) { return false; } return areTypesAssignable (GenericTypeReflector.getArrayComponentType (sourceType), GenericTypeReflector.getArrayComponentType (destinationType)); } public static boolean areGenericTypesAssignable (final Type sourceType, final ParameterizedType destinationType) { // If the source is not assignable to the raw destination type, there is no need to check generic parameters: if (!areClassesAssignable (getRawClass (sourceType), getRawClass (destinationType))) { return false; } return GenericTypeReflector.isSuperType (destinationType, sourceType); } public static Class<?> getRawClass (final Type type) { if (type instanceof Class<?>) { return (Class<?>)type; } else if (type instanceof ParameterizedType) { return (Class<?>)((ParameterizedType)type).getRawType (); } return null; } public static boolean areClassesAssignable (final Class<?> sourceType, final Class<?> destinationType) { if (sourceType == null || destinationType == null) { throw new NullPointerException (); } // When the types are exactly equal, they are also assignable: if (sourceType.equals (destinationType)) { return true; } // Unboxing primitive types is permitted: if (destinationType.isPrimitive () && primitiveToBoxed.get (destinationType).equals (sourceType)) { return true; } // Boxing primitive types is permitted, can convert to boxed type or to object: if (sourceType.isPrimitive () && (primitiveToBoxed.get (sourceType).equals (destinationType) || destinationType.equals (Object.class))) { return true; } // Upcasting of integer and floating point values is permitted: if (destinationType.isPrimitive () && canUpcast (sourceType, destinationType)) { return true; } return destinationType.isAssignableFrom (sourceType); } public static boolean canUpcast (final Class<?> sourceType, final Class<?> destinationType) { final Class<?> type = sourceType.isPrimitive () ? sourceType : boxedToPrimitive.get (sourceType); // If the source type has no primitive representation, a cast is not possible: if (type == null) { return false; } // Check upcasting for integers: if (integerRank.containsKey (type) && integerRank.containsKey (destinationType) && integerRank.get (destinationType) >= integerRank.get (type)) { return true; } // Check upcasting for floats: if (floatRank.containsKey (type) && floatRank.containsKey (destinationType) && floatRank.get (destinationType) >= floatRank.get (type)) { return true; } return false; } }