/* * (c) Copyright 2010-2011 AgileBirds * * This file is part of OpenFlexo. * * OpenFlexo is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OpenFlexo is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenFlexo. If not, see <http://www.gnu.org/licenses/>. * */ package org.openflexo.antar.binding; import java.lang.reflect.GenericArrayType; import java.lang.reflect.GenericDeclaration; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.lang.reflect.WildcardType; import java.util.Collection; import java.util.List; import java.util.Vector; import java.util.logging.Logger; import javax.annotation.Nullable; import org.openflexo.antar.expr.EvaluationType; import com.google.common.primitives.Primitives; public class TypeUtils { static final Logger logger = Logger.getLogger(TypeUtils.class.getPackage().getName()); public static Class getBaseClass(Type aType) { if (aType == null) { return null; } if (aType instanceof CustomType) { return ((CustomType) aType).getBaseClass(); } if (isResolved(aType)) { if (aType instanceof Class) { return (Class) aType; } else if (aType instanceof ParameterizedType) { Type rawType = ((ParameterizedType) aType).getRawType(); if (rawType instanceof Class) { return (Class) rawType; } logger.warning("Not handled: " + aType.getClass().getName()); return null; } else { logger.warning("Not handled: " + aType.getClass().getName()); return null; } } if (aType instanceof WildcardType) { // System.out.println("WildcardType: " + aType); Type[] upperBounds = ((WildcardType) aType).getUpperBounds(); Type[] lowerBounds = ((WildcardType) aType).getLowerBounds(); // System.out.println("upper=" + upperBounds + " size=" + upperBounds.length); // System.out.println("lower=" + upperBounds + " size=" + lowerBounds.length); if (upperBounds.length == 1 && lowerBounds.length == 0) { return getBaseClass(upperBounds[0]); } } if (aType instanceof TypeVariable) { Type[] bounds = ((TypeVariable<?>) aType).getBounds(); if (bounds.length == 1) { return getBaseClass(bounds[0]); } } logger.warning("Not handled: " + aType.getClass().getName()); return null; } public static boolean isClassAncestorOf(Class<?> parentClass, Class<?> childClass) { if (parentClass == null) { return false; } if (childClass == null) { return false; } if (isVoid(parentClass)) { return isVoid(childClass); } if (isVoid(childClass)) { return isVoid(parentClass); } // Special cases if (parentClass == Object.class && childClass.isPrimitive()) { return true; } if (parentClass.isPrimitive()) { return isClassAncestorOf(fromPrimitive(parentClass), childClass); } if (childClass.isPrimitive()) { return isClassAncestorOf(parentClass, fromPrimitive(childClass)); } // Normal case return parentClass.isAssignableFrom(childClass); } public static Class toPrimitive(Class<?> aClass) { if (isDouble(aClass)) { return Double.TYPE; } if (isFloat(aClass)) { return Float.TYPE; } if (isLong(aClass)) { return Long.TYPE; } if (isInteger(aClass)) { return Integer.TYPE; } if (isShort(aClass)) { return Short.TYPE; } if (isByte(aClass)) { return Byte.TYPE; } if (isBoolean(aClass)) { return Boolean.TYPE; } if (isChar(aClass)) { return Character.TYPE; } return aClass; } public static Class fromPrimitive(Class<?> aClass) { if (isDouble(aClass)) { return Double.class; } if (isFloat(aClass)) { return Float.class; } if (isLong(aClass)) { return Long.class; } if (isInteger(aClass)) { return Integer.class; } if (isShort(aClass)) { return Short.class; } if (isByte(aClass)) { return Byte.class; } if (isBoolean(aClass)) { return Boolean.class; } if (isChar(aClass)) { return Character.class; } return aClass; } public static boolean isPrimitive(@Nullable Type type) { return type != null && Primitives.allPrimitiveTypes().contains(type); } public static boolean isWrapperClass(@Nullable Class<?> klass) { return klass != null && Primitives.isWrapperType(klass); } public static boolean isDouble(Type type) { if (type == null) { return false; } return type.equals(Double.class) || type.equals(Double.TYPE); } public static boolean isFloat(Type type) { if (type == null) { return false; } return type.equals(Float.class) || type.equals(Float.TYPE); } public static boolean isInteger(Type type) { if (type == null) { return false; } return type.equals(Integer.class) || type.equals(Integer.TYPE); } public static boolean isLong(Type type) { if (type == null) { return false; } return type.equals(Long.class) || type.equals(Long.TYPE); } public static boolean isObject(Type type) { if (type == null) { return false; } return type.equals(Object.class); } public static boolean isShort(Type type) { if (type == null) { return false; } return type.equals(Short.class) || type.equals(Short.TYPE); } public static boolean isString(Type type) { if (type == null) { return false; } return type.equals(String.class); } public static boolean isVoid(Type type) { if (type == null) { return false; } return type.equals(Void.class) || type.equals(Void.TYPE); } public static boolean isBoolean(Type type) { if (type == null) { return false; } return type.equals(Boolean.class) || type.equals(Boolean.TYPE); } public static boolean isByte(Type type) { if (type == null) { return false; } return type.equals(Byte.class) || type.equals(Byte.TYPE); } public static boolean isChar(Type type) { if (type == null) { return false; } return type.equals(Character.class) || type.equals(Character.TYPE); } public static EvaluationType kindOfType(Type type) { if (isBoolean(type)) { return EvaluationType.BOOLEAN; } else if (isInteger(type) || isLong(type) || isShort(type) || isChar(type) || isByte(type)) { return EvaluationType.ARITHMETIC_INTEGER; } else if (isFloat(type) || isDouble(type)) { return EvaluationType.ARITHMETIC_FLOAT; } else if (isString(type)) { return EvaluationType.STRING; } return EvaluationType.LITERAL; } public static boolean isAssignableTo(Object object, Type type) { return isTypeAssignableFrom(type, object != null ? object.getClass() : null); } public static boolean isAssignableTo(Collection<? extends Object> objects, Type type) { for (Object object : objects) { if (!isAssignableTo(object, type)) { return false; } } return true; } public static boolean isTypeAssignableFrom(Type aType, Type anOtherType) { return isTypeAssignableFrom(aType, anOtherType, true); } /** * Determines if the class or interface represented by supplied <code>aType</code> object is either the same as, or is a superclass or * superinterface of, the class or interface represented by the specified <code>anOtherType</code> parameter. It returns * <code>true</code> if so; otherwise false<br> * This method also tried to resolve generics before to perform the assignability test * * @param aType * @param anOtherType * @param permissive * is a flag indicating if basic conversion between primitive types is allowed: for example, an int may be assign to a float * value after required conversion. * @return */ public static boolean isTypeAssignableFrom(Type aType, Type anOtherType, boolean permissive) { // Test if anOtherType instanceof aType /*if (aType instanceof CustomType || anOtherType instanceof CustomType) { logger.info("Called " + aType + " isAssignableFrom(" + anOtherType + ")"); logger.info("En gros je me demande si " + anOtherType + " est bien une instance de " + aType + " anOtherType est un " + anOtherType.getClass().getSimpleName()); }*/ // If supplied type is null return false if (aType == null || anOtherType == null) { return false; } // Everything could be assigned to Object if (isObject(aType)) { return true; } // Special case for Custom types if (aType instanceof CustomType) { return ((CustomType) aType).isTypeAssignableFrom(anOtherType, permissive); } if (anOtherType instanceof CustomType && isTypeAssignableFrom(aType, ((CustomType) anOtherType).getBaseClass())) { return true; } if (isVoid(aType)) { return isVoid(anOtherType); } if (isBoolean(aType)) { return isBoolean(anOtherType); } if (isDouble(aType)) { return isDouble(anOtherType) || isFloat(anOtherType) || isLong(anOtherType) || isInteger(anOtherType) || isShort(anOtherType) || isChar(anOtherType) || isByte(anOtherType); } if (isFloat(aType)) { return isDouble(anOtherType) && permissive || isFloat(anOtherType) || isLong(anOtherType) || isInteger(anOtherType) || isShort(anOtherType) || isChar(anOtherType) || isByte(anOtherType); } if (isLong(aType)) { return isLong(anOtherType) || isInteger(anOtherType) || isShort(anOtherType) || isChar(anOtherType) || isByte(anOtherType); } if (isInteger(aType)) { return isLong(anOtherType) && permissive || isInteger(anOtherType) || isShort(anOtherType) || isChar(anOtherType) || isByte(anOtherType); } if (isShort(aType)) { return isLong(anOtherType) && permissive || isInteger(anOtherType) && permissive || isShort(anOtherType) || isChar(anOtherType) || isByte(anOtherType); } if (isChar(aType)) { return isLong(anOtherType) && permissive || isInteger(anOtherType) && permissive || isShort(anOtherType) && permissive || isChar(anOtherType) || isByte(anOtherType); } if (isByte(aType)) { return isLong(anOtherType) && permissive || isInteger(anOtherType) && permissive || isShort(anOtherType) && permissive || isChar(anOtherType) || isByte(anOtherType); } if (aType instanceof WildcardType) { if (anOtherType instanceof WildcardType) { // If two wildcards, perform check on both upper bounds return isTypeAssignableFrom(((WildcardType) aType).getUpperBounds()[0], ((WildcardType) anOtherType).getUpperBounds()[0], permissive); } // Perform check on first upper bound only return isTypeAssignableFrom(((WildcardType) aType).getUpperBounds()[0], anOtherType, permissive); } if (aType instanceof GenericArrayType) { // logger.info("Called "+aType+" isAssignableFrom("+anOtherType+")"); // logger.info("anOtherType is a "+anOtherType.getClass()); if (anOtherType instanceof GenericArrayType) { return isTypeAssignableFrom(((GenericArrayType) aType).getGenericComponentType(), ((GenericArrayType) anOtherType).getGenericComponentType(), permissive); } else if (anOtherType instanceof Class && ((Class) anOtherType).isArray()) { return isTypeAssignableFrom(((GenericArrayType) aType).getGenericComponentType(), ((Class) anOtherType).getComponentType(), permissive); } return false; } // Look if we are on same class if (aType instanceof Class && anOtherType instanceof Class) { return isClassAncestorOf((Class) aType, (Class) anOtherType); } if (!isClassAncestorOf(getBaseClass(aType), getBaseClass(anOtherType))) { return false; } // logger.info(""+getBaseClass(aType)+" is ancestor of "+getBaseClass(anOtherType)); if (aType instanceof Class || anOtherType instanceof Class) { // One of two types is not parameterized, we cannot check, return true return true; } if (aType instanceof ParameterizedType && anOtherType instanceof ParameterizedType) { ParameterizedType t1 = (ParameterizedType) aType; ParameterizedType t2 = (ParameterizedType) anOtherType; // Now check that parameters size are the same if (t1.getActualTypeArguments().length != t2.getActualTypeArguments().length) { return false; } // Now, we have to compare parameter per parameter for (int i = 0; i < t1.getActualTypeArguments().length; i++) { Type st1 = t1.getActualTypeArguments()[i]; Type st2 = t2.getActualTypeArguments()[i]; if (!isTypeAssignableFrom(st1, st2, true)) { return false; } } return true; } return org.apache.commons.lang3.reflect.TypeUtils.isAssignable(anOtherType, aType); /*if (getBaseEntity() == type.getBaseEntity()) { // Base entities are the same, let's analyse parameters // If one of both paramters def is empty (parameters are not defined, as before java5) // accept it without performing a test which is impossible to perform if ((getParameters().size() == 0) || (type.getParameters().size() == 0)) return true; // Now check that parameters size are the same if (getParameters().size() != type.getParameters().size()) return false; // Now, we have to compare parameter per parameter for (int i=0; i<getParameters().size(); i++) { DMType localParam = getParameters().elementAt(i); DMType sourceParam = type.getParameters().elementAt(i); if (localParam.getKindOfType() == KindOfType.WILDCARD && localParam.getUpperBounds().size()==1) { DMType resultingSourceParamType; if (sourceParam.getKindOfType() == KindOfType.WILDCARD && sourceParam.getUpperBounds().size()==1) { resultingSourceParamType = sourceParam.getUpperBounds().firstElement().bound; } else { resultingSourceParamType = sourceParam; } if (!localParam.getUpperBounds().firstElement().bound.isAssignableFrom(resultingSourceParamType,permissive)) { return false; } } else if (!localParam.equals(sourceParam)) { return false; } } return true; } // Else it's a true ancestor else { //DMType parentType = makeInstantiatedDMType(type.getBaseEntity().getParentType(),type); DMType parentType = makeInstantiatedDMType(getBaseEntity().getClosestAncestorOf(type.getBaseEntity()),type); return isAssignableFrom(parentType,permissive); }*/ // return false; } public static String simpleRepresentation(Type aType) { if (aType == null) { return "null"; } if (aType instanceof CustomType) { return ((CustomType) aType).simpleRepresentation(); } if (aType instanceof Class) { return ((Class) aType).getSimpleName(); } else if (aType instanceof ParameterizedType) { ParameterizedType t = (ParameterizedType) aType; StringBuilder sb = new StringBuilder(); sb.append(simpleRepresentation(t.getRawType())).append("<"); boolean isFirst = true; for (Type st : t.getActualTypeArguments()) { sb.append(isFirst ? "" : ",").append(simpleRepresentation(st)); isFirst = false; } sb.append(">"); return sb.toString(); } return aType.toString(); } public static String fullQualifiedRepresentation(Type aType) { if (aType == null) { return null; } if (aType instanceof CustomType) { return ((CustomType) aType).fullQualifiedRepresentation(); } if (aType instanceof Class) { return ((Class) aType).getName(); } else if (aType instanceof ParameterizedType) { ParameterizedType t = (ParameterizedType) aType; StringBuilder sb = new StringBuilder(); sb.append(fullQualifiedRepresentation(t.getRawType())).append("<"); boolean isFirst = true; for (Type st : t.getActualTypeArguments()) { sb.append(isFirst ? "" : ",").append(fullQualifiedRepresentation(st)); isFirst = false; } sb.append(">"); return sb.toString(); } return aType.toString(); } public static boolean isResolved(Type type) { return type instanceof Class || type instanceof GenericArrayType || type instanceof ParameterizedType || type instanceof CustomType; } /** * Return flag indicating if this type is considered as generic A generic type is a type that is parameterized with type variable(s). If * this type is resolved but contains a type in it definition containing itself a generic definition, then this type is also generic * (this 'isGeneric' property is recursively transmissible). * * @return a flag indicating whether this type is resolved or not */ public static boolean isGeneric(Type type) { if (type instanceof CustomType) { return false; } if (type instanceof Class) { return false; } if (type instanceof GenericArrayType) { return isGeneric(((GenericArrayType) type).getGenericComponentType()); } if (type instanceof ParameterizedType) { for (Type t : ((ParameterizedType) type).getActualTypeArguments()) { if (isGeneric(t)) { return true; } } return false; } if (type instanceof TypeVariable) { return true; } if (type instanceof WildcardType) { WildcardType w = (WildcardType) type; if (w.getUpperBounds() != null && w.getUpperBounds().length > 0) { for (Type b : w.getUpperBounds()) { if (isGeneric(b)) { return true; } } } if (w.getLowerBounds() != null && w.getLowerBounds().length > 0) { for (Type b : w.getLowerBounds()) { if (isGeneric(b)) { return true; } } } return false; } logger.warning("Unexpected " + type); return false; } /** * Build instanciated DMType considering supplied type is generic (contains TypeVariable definitions) Returns a clone of DMType where * all references to TypeVariable are replaced by values defined in context type. For example, given type=Enumeration<E> and * context=Vector<String>, returns Enumeration<String> If supplied type is not generic, return type value (without cloning!) * * @param type * : type to instanciate * @param context * : context used to instanciate type * @return */ public static Type makeInstantiatedType(Type type, Type context) { if (type == null) { return null; } if (!isGeneric(type)) { return type; } if (type instanceof ParameterizedType) { Type[] actualTypeArguments = new Type[((ParameterizedType) type).getActualTypeArguments().length]; for (int i = 0; i < ((ParameterizedType) type).getActualTypeArguments().length; i++) { actualTypeArguments[i] = makeInstantiatedType(((ParameterizedType) type).getActualTypeArguments()[i], context); } return new ParameterizedTypeImpl((Class) ((ParameterizedType) type).getRawType(), actualTypeArguments); } if (type instanceof GenericArrayType) { return new GenericArrayTypeImpl(makeInstantiatedType(((GenericArrayType) type).getGenericComponentType(), context)); } if (type instanceof TypeVariable) { TypeVariable<GenericDeclaration> tv = (TypeVariable<GenericDeclaration>) type; GenericDeclaration gd = tv.getGenericDeclaration(); // System.out.println("Found type variable "+tv+" name="+tv.getName()+" GD="+tv.getGenericDeclaration()); if (gd instanceof Class) { if (context instanceof ParameterizedType) { for (int i = 0; i < gd.getTypeParameters().length; i++) { if (gd.getTypeParameters()[i].equals(tv)) { // Found matching parameterized type if (i < ((ParameterizedType) context).getActualTypeArguments().length) { // logger.info("********* return instantiatedType for "+type+" context="+context+" gd="+gd); if (!((ParameterizedType) context).getRawType().equals(gd)) { return makeInstantiatedType(type, getSuperType(context)); } return ((ParameterizedType) context).getActualTypeArguments()[i]; } else { logger.warning("Could not retrieve parameterized type " + tv + " with context " + simpleRepresentation(context)); return type; } } } } else if (context instanceof Class && ((Class) context).getGenericSuperclass() != null) { return makeInstantiatedType(type, ((Class) context).getGenericSuperclass()); } } else if (gd instanceof Method) { return type; } logger.warning("Not found type variable " + tv + " in context " + context + " GenericDeclaration=" + tv.getGenericDeclaration()); // throw new InvalidKeyValuePropertyException("Not found type variable "+tv+" in context "+context); return type; } if (type instanceof WildcardType) { WildcardType wt = (WildcardType) type; Type[] upperBounds = new Type[wt.getUpperBounds().length]; for (int i = 0; i < wt.getUpperBounds().length; i++) { upperBounds[i] = makeInstantiatedType(wt.getUpperBounds()[i], context); } Type[] lowerBounds = new Type[wt.getLowerBounds().length]; for (int i = 0; i < wt.getLowerBounds().length; i++) { lowerBounds[i] = makeInstantiatedType(wt.getLowerBounds()[i], context); } return new WilcardTypeImpl(upperBounds, lowerBounds); } logger.warning("Unexpected " + type); return type; } public static Type getSuperType(Type type) { if (type instanceof ParameterizedType) { ParameterizedType myType = (ParameterizedType) type; Type superType = ((Class<?>) myType.getRawType()).getGenericSuperclass(); if (superType instanceof ParameterizedType) { Type[] actualTypeArguments = new Type[((ParameterizedType) superType).getActualTypeArguments().length]; for (int i = 0; i < ((ParameterizedType) superType).getActualTypeArguments().length; i++) { Type tv2 = ((ParameterizedType) superType).getActualTypeArguments()[i]; actualTypeArguments[i] = makeInstantiatedType(tv2, type); } return new ParameterizedTypeImpl(((Class<?>) ((ParameterizedType) type).getRawType()).getSuperclass(), actualTypeArguments); } } else if (type instanceof Class) { return ((Class) type).getGenericSuperclass(); } if (type instanceof CustomType) { return getSuperType(((CustomType) type).getBaseClass()); } return null; } public static Type[] getSuperInterfaceTypes(Type type) { if (type instanceof ParameterizedType) { ParameterizedType myType = (ParameterizedType) type; return ((Class<?>) myType.getRawType()).getGenericInterfaces(); } else if (type instanceof Class) { return ((Class<?>) type).getGenericInterfaces(); } if (type instanceof CustomType) { return getSuperInterfaceTypes(((CustomType) type).getBaseClass()); } return new Type[0]; } public static Object castTo(Object object, Type desiredType) { if (object == null) { return null; } // System.out.println("Object type: "+object.getClass()); // System.out.println("desiredType: "+desiredType); if (object.getClass().equals(desiredType)) { return object; } if (object instanceof Number) { if (TypeUtils.isByte(desiredType)) { return ((Number) object).byteValue(); } if (TypeUtils.isShort(desiredType)) { return ((Number) object).shortValue(); } if (TypeUtils.isInteger(desiredType)) { return ((Number) object).intValue(); } if (TypeUtils.isLong(desiredType)) { return ((Number) object).longValue(); } if (TypeUtils.isDouble(desiredType)) { return ((Number) object).doubleValue(); } if (TypeUtils.isFloat(desiredType)) { return ((Number) object).floatValue(); } } return object; } public static Class<?> getMostSpecializedClass(Collection<Class<?>> someClasses) { if (someClasses.size() == 0) { return null; } if (someClasses.size() == 1) { return someClasses.iterator().next(); } Class<?>[] array = someClasses.toArray(new Class[someClasses.size()]); for (int i = 0; i < someClasses.size(); i++) { for (int j = i + 1; j < someClasses.size(); j++) { Class<?> c1 = array[i]; Class<?> c2 = array[j]; if (c1.isAssignableFrom(c2)) { someClasses.remove(c1); return getMostSpecializedClass(someClasses); } if (c2.isAssignableFrom(c1)) { someClasses.remove(c2); return getMostSpecializedClass(someClasses); } } } // No parent were found, take first item logger.warning("Undefined specializing criteria between " + someClasses); return someClasses.iterator().next(); } // TESTS public static interface ShouldFail { public void test4(short t1, double t2); public void test10(Vector t1, List<String> t2); public void test14(Vector<String> t1, List<String> t2); } public static interface ShouldSucceed { public void test1(Object t1, Object t2); public void test2(int t1, Integer t2); public void test3(float t1, int t2); public void test11(List t1, Vector<String> t2); public void test12(Vector<String> t1, Vector<String> t2); public void test13(List<String> t1, Vector<String> t2); } public static interface TestSuperType { public void test20(MyClass2<Integer, Boolean> t1, MyClass1<Boolean> t2); public void test21(MyClass2<Integer, List<Boolean>> t1, MyClass1<List<Boolean>> t2); public void test22(MyClass3<Integer> t1, MyClass1<List<Integer>> t2); } private static boolean checkFail(Method m) { Type t1 = m.getGenericParameterTypes()[0]; Type t2 = m.getGenericParameterTypes()[1]; System.out.println("checkFail " + (isTypeAssignableFrom(t1, t2, true) ? "NOK " : "OK ") + "Method " + m.getName() + " t1: " + t1 + " of " + t1.getClass().getSimpleName() + " t2: " + t2 + " of " + t2.getClass().getSimpleName()); return isTypeAssignableFrom(t1, t2, true); } private static boolean checkSucceed(Method m) { Type t1 = m.getGenericParameterTypes()[0]; Type t2 = m.getGenericParameterTypes()[1]; System.out.println("checkSucceed " + (isTypeAssignableFrom(t1, t2, true) ? "OK " : "NOK ") + "Method " + m.getName() + " t1: " + t1 + " of " + t1.getClass().getSimpleName() + " t2: " + t2 + " of " + t2.getClass().getSimpleName()); return isTypeAssignableFrom(t1, t2, true); } private static boolean checkSuperType(Method m) { Type t1 = m.getGenericParameterTypes()[0]; Type t2 = m.getGenericParameterTypes()[1]; System.out.println("checkSuperType " + (getSuperType(t1).equals(t2) ? "OK " : "NOK ") + "Method " + m.getName() + " type: " + simpleRepresentation(t1) + " super type: " + simpleRepresentation(t2)); return true; } public static class MyClass1<A> { } public static class MyClass2<B, D> extends MyClass1<D> { } public static class MyClass3<C> extends MyClass1<List<C>> { } public static void main(String[] args) { System.err.println(isTypeAssignableFrom(Number.class, Integer.class)); System.err.println(org.apache.commons.lang3.reflect.TypeUtils.isAssignable(Integer.class, Number.class)); } }