diff options
Diffstat (limited to 'src/jdk/internal/dynalink/support/TypeUtilities.java')
-rw-r--r-- | src/jdk/internal/dynalink/support/TypeUtilities.java | 212 |
1 files changed, 151 insertions, 61 deletions
diff --git a/src/jdk/internal/dynalink/support/TypeUtilities.java b/src/jdk/internal/dynalink/support/TypeUtilities.java index 57fea990..bf4771b2 100644 --- a/src/jdk/internal/dynalink/support/TypeUtilities.java +++ b/src/jdk/internal/dynalink/support/TypeUtilities.java @@ -106,38 +106,49 @@ public class TypeUtilities { } /** - * Given two types represented by c1 and c2, returns a type that is their most specific common superclass or - * superinterface. + * Given two types represented by c1 and c2, returns a type that is their most specific common supertype for + * purposes of lossless conversions. * * @param c1 one type * @param c2 another type - * @return their most common superclass or superinterface. If they have several unrelated superinterfaces as their - * most specific common type, or the types themselves are completely unrelated interfaces, {@link java.lang.Object} - * is returned. + * @return their most common superclass or superinterface for purposes of lossless conversions. If they have several + * unrelated superinterfaces as their most specific common type, or the types themselves are completely + * unrelated interfaces, {@link java.lang.Object} is returned. */ - public static Class<?> getMostSpecificCommonType(Class<?> c1, Class<?> c2) { + public static Class<?> getCommonLosslessConversionType(final Class<?> c1, final Class<?> c2) { if(c1 == c2) { return c1; + } else if(isConvertibleWithoutLoss(c2, c1)) { + return c1; + } else if(isConvertibleWithoutLoss(c1, c2)) { + return c2; + } + if(c1 == void.class) { + return c2; + } else if(c2 == void.class) { + return c1; } - Class<?> c3 = c2; - if(c3.isPrimitive()) { - if(c3 == Byte.TYPE) - c3 = Byte.class; - else if(c3 == Short.TYPE) - c3 = Short.class; - else if(c3 == Character.TYPE) - c3 = Character.class; - else if(c3 == Integer.TYPE) - c3 = Integer.class; - else if(c3 == Float.TYPE) - c3 = Float.class; - else if(c3 == Long.TYPE) - c3 = Long.class; - else if(c3 == Double.TYPE) - c3 = Double.class; - } - Set<Class<?>> a1 = getAssignables(c1, c3); - Set<Class<?>> a2 = getAssignables(c3, c1); + if(c1.isPrimitive() && c2.isPrimitive()) { + if((c1 == byte.class && c2 == char.class) || (c1 == char.class && c2 == byte.class)) { + // byte + char = int + return int.class; + } else if((c1 == short.class && c2 == char.class) || (c1 == char.class && c2 == short.class)) { + // short + char = int + return int.class; + } else if((c1 == int.class && c2 == float.class) || (c1 == float.class && c2 == int.class)) { + // int + float = double + return double.class; + } + } + // For all other cases. This will handle long + (float|double) = Number case as well as boolean + anything = Object case too. + return getMostSpecificCommonTypeUnequalNonprimitives(c1, c2); + } + + private static Class<?> getMostSpecificCommonTypeUnequalNonprimitives(final Class<?> c1, final Class<?> c2) { + final Class<?> npc1 = c1.isPrimitive() ? getWrapperType(c1) : c1; + final Class<?> npc2 = c2.isPrimitive() ? getWrapperType(c2) : c2; + final Set<Class<?>> a1 = getAssignables(npc1, npc2); + final Set<Class<?>> a2 = getAssignables(npc2, npc1); a1.retainAll(a2); if(a1.isEmpty()) { // Can happen when at least one of the arguments is an interface, @@ -148,10 +159,10 @@ public class TypeUtilities { // thank to interfaces. I.e., if you call this method for String.class // and Number.class, you'll have Comparable, Serializable, and Object // as maximal elements. - List<Class<?>> max = new ArrayList<>(); - outer: for(Class<?> clazz: a1) { - for(Iterator<Class<?>> maxiter = max.iterator(); maxiter.hasNext();) { - Class<?> maxClazz = maxiter.next(); + final List<Class<?>> max = new ArrayList<>(); + outer: for(final Class<?> clazz: a1) { + for(final Iterator<Class<?>> maxiter = max.iterator(); maxiter.hasNext();) { + final Class<?> maxClazz = maxiter.next(); if(isSubtype(maxClazz, clazz)) { // It can't be maximal, if there's already a more specific // maximal than it. @@ -168,26 +179,26 @@ public class TypeUtilities { max.add(clazz); } if(max.size() > 1) { - return OBJECT_CLASS; + return Object.class; } return max.get(0); } - private static Set<Class<?>> getAssignables(Class<?> c1, Class<?> c2) { - Set<Class<?>> s = new HashSet<>(); + private static Set<Class<?>> getAssignables(final Class<?> c1, final Class<?> c2) { + final Set<Class<?>> s = new HashSet<>(); collectAssignables(c1, c2, s); return s; } - private static void collectAssignables(Class<?> c1, Class<?> c2, Set<Class<?>> s) { + private static void collectAssignables(final Class<?> c1, final Class<?> c2, final Set<Class<?>> s) { if(c1.isAssignableFrom(c2)) { s.add(c1); } - Class<?> sc = c1.getSuperclass(); + final Class<?> sc = c1.getSuperclass(); if(sc != null) { collectAssignables(sc, c2, s); } - Class<?>[] itf = c1.getInterfaces(); + final Class<?>[] itf = c1.getInterfaces(); for(int i = 0; i < itf.length; ++i) { collectAssignables(itf[i], c2, s); } @@ -210,17 +221,17 @@ public class TypeUtilities { return Collections.unmodifiableMap(wrapperTypes); } - private static Map<String, Class<?>> createClassNameMapping(Collection<Class<?>> classes) { + private static Map<String, Class<?>> createClassNameMapping(final Collection<Class<?>> classes) { final Map<String, Class<?>> map = new HashMap<>(); - for(Class<?> clazz: classes) { + for(final Class<?> clazz: classes) { map.put(clazz.getName(), clazz); } return map; } - private static <K, V> Map<V, K> invertMap(Map<K, V> map) { + private static <K, V> Map<V, K> invertMap(final Map<K, V> map) { final Map<V, K> inverted = new IdentityHashMap<>(map.size()); - for(Map.Entry<K, V> entry: map.entrySet()) { + for(final Map.Entry<K, V> entry: map.entrySet()) { inverted.put(entry.getValue(), entry.getKey()); } return Collections.unmodifiableMap(inverted); @@ -232,30 +243,59 @@ public class TypeUtilities { * {@link #isSubtype(Class, Class)}) as well as boxing conversion (JLS 5.1.7) optionally followed by widening * reference conversion and unboxing conversion (JLS 5.1.8) optionally followed by widening primitive conversion. * - * @param callSiteType the parameter type at the call site - * @param methodType the parameter type in the method declaration - * @return true if callSiteType is method invocation convertible to the methodType. + * @param sourceType the type being converted from (call site type for parameter types, method type for return types) + * @param targetType the parameter type being converted to (method type for parameter types, call site type for return types) + * @return true if source type is method invocation convertible to target type. */ - public static boolean isMethodInvocationConvertible(Class<?> callSiteType, Class<?> methodType) { - if(methodType.isAssignableFrom(callSiteType)) { + public static boolean isMethodInvocationConvertible(final Class<?> sourceType, final Class<?> targetType) { + if(targetType.isAssignableFrom(sourceType)) { return true; } - if(callSiteType.isPrimitive()) { - if(methodType.isPrimitive()) { - return isProperPrimitiveSubtype(callSiteType, methodType); + if(sourceType.isPrimitive()) { + if(targetType.isPrimitive()) { + return isProperPrimitiveSubtype(sourceType, targetType); } // Boxing + widening reference conversion - return methodType.isAssignableFrom(WRAPPER_TYPES.get(callSiteType)); + assert WRAPPER_TYPES.get(sourceType) != null : sourceType.getName(); + return targetType.isAssignableFrom(WRAPPER_TYPES.get(sourceType)); } - if(methodType.isPrimitive()) { - final Class<?> unboxedCallSiteType = PRIMITIVE_TYPES.get(callSiteType); + if(targetType.isPrimitive()) { + final Class<?> unboxedCallSiteType = PRIMITIVE_TYPES.get(sourceType); return unboxedCallSiteType != null - && (unboxedCallSiteType == methodType || isProperPrimitiveSubtype(unboxedCallSiteType, methodType)); + && (unboxedCallSiteType == targetType || isProperPrimitiveSubtype(unboxedCallSiteType, targetType)); } return false; } /** + * Determines whether a type can be converted to another without losing any + * precision. + * + * @param sourceType the source type + * @param targetType the target type + * @return true if lossless conversion is possible + */ + public static boolean isConvertibleWithoutLoss(final Class<?> sourceType, final Class<?> targetType) { + if(targetType.isAssignableFrom(sourceType)) { + return true; + } + if(sourceType.isPrimitive()) { + if(sourceType == void.class) { + return false; // Void can't be losslessly represented by any type + } + if(targetType.isPrimitive()) { + return isProperPrimitiveLosslessSubtype(sourceType, targetType); + } + // Boxing + widening reference conversion + assert WRAPPER_TYPES.get(sourceType) != null : sourceType.getName(); + return targetType.isAssignableFrom(WRAPPER_TYPES.get(sourceType)); + } + // Can't convert from any non-primitive type to any primitive type without data loss because of null. + // Also, can't convert non-assignable reference types. + return false; + } + + /** * Determines whether one type can be potentially converted to another type at runtime. Allows a conversion between * any subtype and supertype in either direction, and also allows a conversion between any two primitive types, as * well as between any primitive type and any reference type that can hold a boxed primitive. @@ -264,9 +304,9 @@ public class TypeUtilities { * @param methodType the parameter type in the method declaration * @return true if callSiteType is potentially convertible to the methodType. */ - public static boolean isPotentiallyConvertible(Class<?> callSiteType, Class<?> methodType) { + public static boolean isPotentiallyConvertible(final Class<?> callSiteType, final Class<?> methodType) { // Widening or narrowing reference conversion - if(methodType.isAssignableFrom(callSiteType) || callSiteType.isAssignableFrom(methodType)) { + if(areAssignable(callSiteType, methodType)) { return true; } if(callSiteType.isPrimitive()) { @@ -287,6 +327,16 @@ public class TypeUtilities { } /** + * Returns true if either of the types is assignable from the other. + * @param c1 one of the types + * @param c2 another one of the types + * @return true if either c1 is assignable from c2 or c2 is assignable from c1. + */ + public static boolean areAssignable(final Class<?> c1, final Class<?> c2) { + return c1.isAssignableFrom(c2) || c2.isAssignableFrom(c1); + } + + /** * Determines whether one type is a subtype of another type, as per JLS 4.10 "Subtyping". Note: this is not strict * or proper subtype, therefore true is also returned for identical types; to be completely precise, it allows * identity conversion (JLS 5.1.1), widening primitive conversion (JLS 5.1.2) and widening reference conversion (JLS @@ -297,7 +347,7 @@ public class TypeUtilities { * @return true if subType can be converted by identity conversion, widening primitive conversion, or widening * reference conversion to superType. */ - public static boolean isSubtype(Class<?> subType, Class<?> superType) { + public static boolean isSubtype(final Class<?> subType, final Class<?> superType) { // Covers both JLS 4.10.2 "Subtyping among Class and Interface Types" // and JLS 4.10.3 "Subtyping among Array Types", as well as primitive // type identity. @@ -328,7 +378,7 @@ public class TypeUtilities { * @param superType the supposed supertype * @return true if subType is a proper (not identical to) primitive subtype of the superType */ - private static boolean isProperPrimitiveSubtype(Class<?> subType, Class<?> superType) { + private static boolean isProperPrimitiveSubtype(final Class<?> subType, final Class<?> superType) { if(superType == boolean.class || subType == boolean.class) { return false; } @@ -353,6 +403,37 @@ public class TypeUtilities { return false; } + /** + * Similar to {@link #isProperPrimitiveSubtype(Class, Class)}, except it disallows conversions from int and long to + * float, and from long to double, as those can lose precision. It also disallows conversion from and to char and + * anything else (similar to boolean) as char is not meant to be an arithmetic type. + * @param subType the supposed subtype + * @param superType the supposed supertype + * @return true if subType is a proper (not identical to) primitive subtype of the superType that can be represented + * by the supertype without no precision loss. + */ + private static boolean isProperPrimitiveLosslessSubtype(final Class<?> subType, final Class<?> superType) { + if(superType == boolean.class || subType == boolean.class) { + return false; + } + if(superType == char.class || subType == char.class) { + return false; + } + if(subType == byte.class) { + return true; + } + if(subType == short.class) { + return superType != byte.class; + } + if(subType == int.class) { + return superType == long.class || superType == double.class; + } + if(subType == float.class) { + return superType == double.class; + } + return false; + } + private static final Map<Class<?>, Class<?>> WRAPPER_TO_PRIMITIVE_TYPES = createWrapperToPrimitiveTypes(); private static Map<Class<?>, Class<?>> createWrapperToPrimitiveTypes() { @@ -384,13 +465,13 @@ public class TypeUtilities { return classes.keySet(); } - private static void addClassHierarchy(Map<Class<?>, Class<?>> map, Class<?> clazz) { + private static void addClassHierarchy(final Map<Class<?>, Class<?>> map, final Class<?> clazz) { if(clazz == null) { return; } map.put(clazz, clazz); addClassHierarchy(map, clazz.getSuperclass()); - for(Class<?> itf: clazz.getInterfaces()) { + for(final Class<?> itf: clazz.getInterfaces()) { addClassHierarchy(map, itf); } } @@ -402,7 +483,7 @@ public class TypeUtilities { * @return true if the class can be assigned from any boxed primitive. Basically, it is true if the class is any * primitive wrapper class, or a superclass or superinterface of any primitive wrapper class. */ - private static boolean isAssignableFromBoxedPrimitive(Class<?> clazz) { + private static boolean isAssignableFromBoxedPrimitive(final Class<?> clazz) { return PRIMITIVE_WRAPPER_TYPES.contains(clazz); } @@ -413,7 +494,7 @@ public class TypeUtilities { * @return the class representing the primitive type, or null if the name does not correspond to a primitive type * or is "void". */ - public static Class<?> getPrimitiveTypeByName(String name) { + public static Class<?> getPrimitiveTypeByName(final String name) { return PRIMITIVE_TYPES_BY_NAME.get(name); } @@ -424,7 +505,7 @@ public class TypeUtilities { * @param wrapperType the class object representing a wrapper for a primitive type * @return the class object representing the primitive type, or null if the passed class is not a primitive wrapper. */ - public static Class<?> getPrimitiveType(Class<?> wrapperType) { + public static Class<?> getPrimitiveType(final Class<?> wrapperType) { return WRAPPER_TO_PRIMITIVE_TYPES.get(wrapperType); } @@ -436,7 +517,16 @@ public class TypeUtilities { * @param primitiveType the class object representing a primitive type * @return the class object representing the wrapper type, or null if the passed class is not a primitive. */ - public static Class<?> getWrapperType(Class<?> primitiveType) { + public static Class<?> getWrapperType(final Class<?> primitiveType) { return WRAPPER_TYPES.get(primitiveType); } + + /** + * Returns true if the passed type is a wrapper for a primitive type. + * @param type the examined type + * @return true if the passed type is a wrapper for a primitive type. + */ + public static boolean isWrapperType(final Class<?> type) { + return PRIMITIVE_TYPES.containsKey(type); + } } |