aboutsummaryrefslogtreecommitdiff
path: root/src/jdk/internal/dynalink/support/TypeUtilities.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/jdk/internal/dynalink/support/TypeUtilities.java')
-rw-r--r--src/jdk/internal/dynalink/support/TypeUtilities.java212
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);
+ }
}