8139887: Reduce visibility of few methods in TypeUtilities and Guards API
authorattila
Tue, 20 Oct 2015 23:33:39 +0200
changeset 33338 faf6471e1cc8
parent 33337 af3fea63e008
child 33339 334cd3ebfa5e
8139887: Reduce visibility of few methods in TypeUtilities and Guards API Reviewed-by: hannesw, sundar
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/internal/dynalink/ClassMap.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/internal/dynalink/beans/AbstractJavaLinker.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/internal/dynalink/beans/ClassString.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/internal/dynalink/beans/OverloadedDynamicMethod.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/internal/dynalink/beans/OverloadedMethod.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/internal/dynalink/internal/InternalTypeUtilities.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/internal/dynalink/linker/support/Guards.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/internal/dynalink/linker/support/TypeUtilities.java
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/internal/dynalink/ClassMap.java	Tue Oct 20 23:33:18 2015 +0200
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/internal/dynalink/ClassMap.java	Tue Oct 20 23:33:39 2015 +0200
@@ -91,7 +91,7 @@
 import java.util.WeakHashMap;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
-import jdk.internal.dynalink.linker.support.Guards;
+import jdk.internal.dynalink.internal.InternalTypeUtilities;
 
 /**
  * A dual map that can either strongly or weakly reference a given class depending on whether the class is visible from
@@ -153,7 +153,7 @@
         final Boolean canReferenceDirectly = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
             @Override
             public Boolean run() {
-                return Guards.canReferenceDirectly(classLoader, clazz.getClassLoader());
+                return InternalTypeUtilities.canReferenceDirectly(classLoader, clazz.getClassLoader());
             }
         }, ClassLoaderGetterContextProvider.GET_CLASS_LOADER_CONTEXT);
 
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/internal/dynalink/beans/AbstractJavaLinker.java	Tue Oct 20 23:33:18 2015 +0200
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/internal/dynalink/beans/AbstractJavaLinker.java	Tue Oct 20 23:33:39 2015 +0200
@@ -99,13 +99,13 @@
 import java.util.Map;
 import jdk.internal.dynalink.CallSiteDescriptor;
 import jdk.internal.dynalink.beans.GuardedInvocationComponent.ValidationType;
+import jdk.internal.dynalink.internal.InternalTypeUtilities;
 import jdk.internal.dynalink.linker.GuardedInvocation;
 import jdk.internal.dynalink.linker.GuardingDynamicLinker;
 import jdk.internal.dynalink.linker.LinkRequest;
 import jdk.internal.dynalink.linker.LinkerServices;
 import jdk.internal.dynalink.linker.support.Guards;
 import jdk.internal.dynalink.linker.support.Lookup;
-import jdk.internal.dynalink.linker.support.TypeUtilities;
 import sun.reflect.CallerSensitive;
 
 /**
@@ -689,7 +689,7 @@
                 assertParameterCount(callSiteDescriptor, 2);
                 final GuardedInvocationComponent nextComponent = getGuardedInvocationComponent(callSiteDescriptor,
                         linkerServices, ops);
-                if(nextComponent == null || !TypeUtilities.areAssignable(DynamicMethod.class,
+                if(nextComponent == null || !InternalTypeUtilities.areAssignable(DynamicMethod.class,
                         nextComponent.getGuardedInvocation().getInvocation().type().returnType())) {
                     // No next component operation, or it can never produce a dynamic method; just return a component
                     // for this operation.
@@ -756,7 +756,7 @@
     static MethodPair matchReturnTypes(final MethodHandle m1, final MethodHandle m2) {
         final MethodType type1 = m1.type();
         final MethodType type2 = m2.type();
-        final Class<?> commonRetType = TypeUtilities.getCommonLosslessConversionType(type1.returnType(),
+        final Class<?> commonRetType = InternalTypeUtilities.getCommonLosslessConversionType(type1.returnType(),
                 type2.returnType());
         return new MethodPair(
                 m1.asType(type1.changeReturnType(commonRetType)),
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/internal/dynalink/beans/ClassString.java	Tue Oct 20 23:33:18 2015 +0200
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/internal/dynalink/beans/ClassString.java	Tue Oct 20 23:33:39 2015 +0200
@@ -90,8 +90,8 @@
 import java.security.PrivilegedAction;
 import java.util.LinkedList;
 import java.util.List;
+import jdk.internal.dynalink.internal.InternalTypeUtilities;
 import jdk.internal.dynalink.linker.LinkerServices;
-import jdk.internal.dynalink.linker.support.Guards;
 import jdk.internal.dynalink.linker.support.TypeUtilities;
 
 /**
@@ -152,7 +152,7 @@
             @Override
             public Boolean run() {
                 for(final Class<?> clazz: classes) {
-                    if(!Guards.canReferenceDirectly(classLoader, clazz.getClassLoader())) {
+                    if(!InternalTypeUtilities.canReferenceDirectly(classLoader, clazz.getClassLoader())) {
                         return false;
                     }
                 }
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/internal/dynalink/beans/OverloadedDynamicMethod.java	Tue Oct 20 23:33:18 2015 +0200
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/internal/dynalink/beans/OverloadedDynamicMethod.java	Tue Oct 20 23:33:39 2015 +0200
@@ -93,13 +93,16 @@
 import java.text.Collator;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.IdentityHashMap;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 import jdk.internal.dynalink.CallSiteDescriptor;
 import jdk.internal.dynalink.beans.ApplicableOverloadedMethods.ApplicabilityTest;
+import jdk.internal.dynalink.internal.InternalTypeUtilities;
 import jdk.internal.dynalink.linker.LinkerServices;
-import jdk.internal.dynalink.linker.support.TypeUtilities;
 
 /**
  * Represents a group of {@link SingleDynamicMethod} objects that represents all overloads of a particular name (or all
@@ -344,7 +347,7 @@
 
     private static boolean isApplicableDynamically(final LinkerServices linkerServices, final Class<?> callSiteType,
             final Class<?> methodType) {
-        return TypeUtilities.isPotentiallyConvertible(callSiteType, methodType)
+        return isPotentiallyConvertible(callSiteType, methodType)
                 || linkerServices.canConvert(callSiteType, methodType);
     }
 
@@ -365,4 +368,72 @@
     private boolean constructorFlagConsistent(final SingleDynamicMethod method) {
         return methods.isEmpty()? true : (methods.getFirst().isConstructor() == method.isConstructor());
     }
+
+    /**
+     * 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.
+     *
+     * @param callSiteType the parameter type at the call site
+     * @param methodType the parameter type in the method declaration
+     * @return true if callSiteType is potentially convertible to the methodType.
+     */
+    private static boolean isPotentiallyConvertible(final Class<?> callSiteType, final Class<?> methodType) {
+        // Widening or narrowing reference conversion
+        if(InternalTypeUtilities.areAssignable(callSiteType, methodType)) {
+            return true;
+        }
+        if(callSiteType.isPrimitive()) {
+            // Allow any conversion among primitives, as well as from any
+            // primitive to any type that can receive a boxed primitive.
+            // TODO: narrow this a bit, i.e. allow, say, boolean to Character?
+            // MethodHandles.convertArguments() allows it, so we might need to
+            // too.
+            return methodType.isPrimitive() || isAssignableFromBoxedPrimitive(methodType);
+        }
+        if(methodType.isPrimitive()) {
+            // Allow conversion from any reference type that can contain a
+            // boxed primitive to any primitive.
+            // TODO: narrow this a bit too?
+            return isAssignableFromBoxedPrimitive(callSiteType);
+        }
+        return false;
+    }
+
+    private static final Set<Class<?>> PRIMITIVE_WRAPPER_TYPES = createPrimitiveWrapperTypes();
+
+    private static Set<Class<?>> createPrimitiveWrapperTypes() {
+        final Map<Class<?>, Class<?>> classes = new IdentityHashMap<>();
+        addClassHierarchy(classes, Boolean.class);
+        addClassHierarchy(classes, Byte.class);
+        addClassHierarchy(classes, Character.class);
+        addClassHierarchy(classes, Short.class);
+        addClassHierarchy(classes, Integer.class);
+        addClassHierarchy(classes, Long.class);
+        addClassHierarchy(classes, Float.class);
+        addClassHierarchy(classes, Double.class);
+        return classes.keySet();
+    }
+
+    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(final Class<?> itf: clazz.getInterfaces()) {
+            addClassHierarchy(map, itf);
+        }
+    }
+
+    /**
+     * Returns true if the class can be assigned from any boxed primitive.
+     *
+     * @param clazz the class
+     * @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(final Class<?> clazz) {
+        return PRIMITIVE_WRAPPER_TYPES.contains(clazz);
+    }
 }
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/internal/dynalink/beans/OverloadedMethod.java	Tue Oct 20 23:33:18 2015 +0200
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/internal/dynalink/beans/OverloadedMethod.java	Tue Oct 20 23:33:39 2015 +0200
@@ -91,9 +91,9 @@
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
+import jdk.internal.dynalink.internal.InternalTypeUtilities;
 import jdk.internal.dynalink.linker.LinkerServices;
 import jdk.internal.dynalink.linker.support.Lookup;
-import jdk.internal.dynalink.linker.support.TypeUtilities;
 
 /**
  * Represents a subset of overloaded methods for a certain method name on a certain class. It can be either a fixarg or
@@ -273,7 +273,7 @@
         final Iterator<MethodHandle> it = methodHandles.iterator();
         Class<?> retType = it.next().type().returnType();
         while(it.hasNext()) {
-            retType = TypeUtilities.getCommonLosslessConversionType(retType, it.next().type().returnType());
+            retType = InternalTypeUtilities.getCommonLosslessConversionType(retType, it.next().type().returnType());
         }
         return retType;
     }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/internal/dynalink/internal/InternalTypeUtilities.java	Tue Oct 20 23:33:39 2015 +0200
@@ -0,0 +1,187 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code 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
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.internal.dynalink.internal;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import jdk.internal.dynalink.linker.support.TypeUtilities;
+
+/**
+ * Various static utility methods for testing type relationships; internal to Dynalink.
+ */
+public class InternalTypeUtilities {
+    private InternalTypeUtilities() {
+    }
+
+    /**
+     * Returns true if either of the types is assignable from the other.
+     * @param c1 one type
+     * @param c2 another type
+     * @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);
+    }
+
+    /**
+     * Return true if it is safe to strongly reference a class from the referred
+     * class loader from a class associated with the referring class loader
+     * without risking a class loader memory leak. Generally, it is only safe
+     * to reference classes from the same or ancestor class loader. {@code null}
+     * indicates the system class loader; classes from it can always be
+     * directly referenced, and it can only directly reference classes from
+     * itself. This method can be used by language runtimes to ensure they are
+     * using weak references in their linkages when they need to link to methods
+     * in unrelated class loaders.
+     *
+     * @param referrerLoader the referrer class loader.
+     * @param referredLoader the referred class loader
+     * @return true if it is safe to strongly reference the class from referred
+     * in referred.
+     * @throws SecurityException if the caller does not have the
+     * {@code RuntimePermission("getClassLoader")} permission and the method
+     * needs to traverse the parent class loader chain.
+     */
+    public static boolean canReferenceDirectly(final ClassLoader referrerLoader, final ClassLoader referredLoader) {
+        if(referredLoader == null) {
+            // Can always refer directly to a system class
+            return true;
+        }
+        if(referrerLoader == null) {
+            // System classes can't refer directly to any non-system class
+            return false;
+        }
+        // Otherwise, can only refer directly to classes residing in same or
+        // parent class loader.
+
+        ClassLoader referrer = referrerLoader;
+        do {
+            if(referrer == referredLoader) {
+                return true;
+            }
+            referrer = referrer.getParent();
+        } while(referrer != null);
+        return false;
+    }
+
+    /**
+     * 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 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<?> getCommonLosslessConversionType(final Class<?> c1, final Class<?> c2) {
+        if(c1 == c2) {
+            return c1;
+        } else if (c1 == void.class || c2 == void.class) {
+            return Object.class;
+        } else if(TypeUtilities.isConvertibleWithoutLoss(c2, c1)) {
+            return c1;
+        } else if(TypeUtilities.isConvertibleWithoutLoss(c1, c2)) {
+            return c2;
+        } else 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() ? TypeUtilities.getWrapperType(c1) : c1;
+        final Class<?> npc2 = c2.isPrimitive() ? TypeUtilities.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,
+            // as they don't have Object at the root of their hierarchy.
+            return Object.class;
+        }
+        // Gather maximally specific elements. Yes, there can be more than one
+        // 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.
+        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(TypeUtilities.isSubtype(maxClazz, clazz)) {
+                    // It can't be maximal, if there's already a more specific
+                    // maximal than it.
+                    continue outer;
+                }
+                if(TypeUtilities.isSubtype(clazz, maxClazz)) {
+                    // If it's more specific than a currently maximal element,
+                    // that currently maximal is no longer a maximal.
+                    maxiter.remove();
+                }
+            }
+            // If we get here, no current maximal is more specific than the
+            // current class, so it is considered maximal as well
+            max.add(clazz);
+        }
+        if(max.size() > 1) {
+            return Object.class;
+        }
+        return max.get(0);
+    }
+
+    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(final Class<?> c1, final Class<?> c2, final Set<Class<?>> s) {
+        if(c1.isAssignableFrom(c2)) {
+            s.add(c1);
+        }
+        final Class<?> sc = c1.getSuperclass();
+        if(sc != null) {
+            collectAssignables(sc, c2, s);
+        }
+        final Class<?>[] itf = c1.getInterfaces();
+        for(int i = 0; i < itf.length; ++i) {
+            collectAssignables(itf[i], c2, s);
+        }
+    }
+}
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/internal/dynalink/linker/support/Guards.java	Tue Oct 20 23:33:18 2015 +0200
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/internal/dynalink/linker/support/Guards.java	Tue Oct 20 23:33:39 2015 +0200
@@ -183,36 +183,6 @@
         return asType(IS_ARRAY, pos, type);
     }
 
-    /**
-     * Return true if it is safe to strongly reference a class from the referred class loader from a class associated
-     * with the referring class loader without risking a class loader memory leak.
-     *
-     * @param referrerLoader the referrer class loader
-     * @param referredLoader the referred class loader
-     * @return true if it is safe to strongly reference the class
-     */
-    public static boolean canReferenceDirectly(final ClassLoader referrerLoader, final ClassLoader referredLoader) {
-        if(referredLoader == null) {
-            // Can always refer directly to a system class
-            return true;
-        }
-        if(referrerLoader == null) {
-            // System classes can't refer directly to any non-system class
-            return false;
-        }
-        // Otherwise, can only refer directly to classes residing in same or
-        // parent class loader.
-
-        ClassLoader referrer = referrerLoader;
-        do {
-            if(referrer == referredLoader) {
-                return true;
-            }
-            referrer = referrer.getParent();
-        } while(referrer != null);
-        return false;
-    }
-
     private static MethodHandle getClassBoundArgumentTest(final MethodHandle test, final Class<?> clazz, final int pos, final MethodType type) {
         // Bind the class to the first argument of the test
         return asType(test.bindTo(clazz), pos, type);
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/internal/dynalink/linker/support/TypeUtilities.java	Tue Oct 20 23:33:18 2015 +0200
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/internal/dynalink/linker/support/TypeUtilities.java	Tue Oct 20 23:33:39 2015 +0200
@@ -83,19 +83,16 @@
 
 package jdk.internal.dynalink.linker.support;
 
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.IdentityHashMap;
-import java.util.Iterator;
-import java.util.List;
 import java.util.Map;
-import java.util.Set;
+import jdk.internal.dynalink.DynamicLinkerFactory;
+import jdk.internal.dynalink.linker.MethodTypeConversionStrategy;
 
 /**
- * Various static utility methods for testing type relationships.
+ * Various static utility methods for working with Java types.
  */
 public class TypeUtilities {
     static final Class<Object> OBJECT_CLASS = Object.class;
@@ -103,107 +100,13 @@
     private TypeUtilities() {
     }
 
-    /**
-     * 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 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<?> getCommonLosslessConversionType(final Class<?> c1, final Class<?> c2) {
-        if(c1 == c2) {
-            return c1;
-        } else if (c1 == void.class || c2 == void.class) {
-            return Object.class;
-        } else if(isConvertibleWithoutLoss(c2, c1)) {
-            return c1;
-        } else if(isConvertibleWithoutLoss(c1, c2)) {
-            return c2;
-        } else 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,
-            // as they don't have Object at the root of their hierarchy.
-            return Object.class;
-        }
-        // Gather maximally specific elements. Yes, there can be more than one
-        // 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.
-        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.
-                    continue outer;
-                }
-                if(isSubtype(clazz, maxClazz)) {
-                    // If it's more specific than a currently maximal element,
-                    // that currently maximal is no longer a maximal.
-                    maxiter.remove();
-                }
-            }
-            // If we get here, no current maximal is more specific than the
-            // current class, so it is considered maximal as well
-            max.add(clazz);
-        }
-        if(max.size() > 1) {
-            return Object.class;
-        }
-        return max.get(0);
-    }
-
-    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(final Class<?> c1, final Class<?> c2, final Set<Class<?>> s) {
-        if(c1.isAssignableFrom(c2)) {
-            s.add(c1);
-        }
-        final Class<?> sc = c1.getSuperclass();
-        if(sc != null) {
-            collectAssignables(sc, c2, s);
-        }
-        final Class<?>[] itf = c1.getInterfaces();
-        for(int i = 0; i < itf.length; ++i) {
-            collectAssignables(itf[i], c2, s);
-        }
-    }
-
     private static final Map<Class<?>, Class<?>> WRAPPER_TYPES = createWrapperTypes();
     private static final Map<Class<?>, Class<?>> PRIMITIVE_TYPES = invertMap(WRAPPER_TYPES);
     private static final Map<String, Class<?>> PRIMITIVE_TYPES_BY_NAME = createClassNameMapping(WRAPPER_TYPES.keySet());
 
     private static Map<Class<?>, Class<?>> createWrapperTypes() {
         final Map<Class<?>, Class<?>> wrapperTypes = new IdentityHashMap<>(8);
+        wrapperTypes.put(Void.TYPE, Void.class);
         wrapperTypes.put(Boolean.TYPE, Boolean.class);
         wrapperTypes.put(Byte.TYPE, Byte.class);
         wrapperTypes.put(Character.TYPE, Character.class);
@@ -249,23 +152,32 @@
             if(targetType.isPrimitive()) {
                 return isProperPrimitiveSubtype(sourceType, targetType);
             }
-            // Boxing + widening reference conversion
-            assert WRAPPER_TYPES.get(sourceType) != null : sourceType.getName();
-            return targetType.isAssignableFrom(WRAPPER_TYPES.get(sourceType));
+            return isBoxingAndWideningReferenceConversion(sourceType, targetType);
         }
         if(targetType.isPrimitive()) {
-            final Class<?> unboxedCallSiteType = PRIMITIVE_TYPES.get(sourceType);
+            final Class<?> unboxedCallSiteType = getPrimitiveType(sourceType);
             return unboxedCallSiteType != null
                     && (unboxedCallSiteType == targetType || isProperPrimitiveSubtype(unboxedCallSiteType, targetType));
         }
         return false;
     }
 
+    private static boolean isBoxingAndWideningReferenceConversion(final Class<?> sourceType, final Class<?> targetType) {
+        final Class<?> wrapperType = getWrapperType(sourceType);
+        assert wrapperType != null : sourceType.getName();
+        return targetType.isAssignableFrom(wrapperType);
+    }
+
     /**
-     * Determines whether a type can be converted to another without losing any precision. As a special case,
-     * void is considered convertible only to Object and void, while anything can be converted to void. This
-     * is because a target type of void means we don't care about the value, so the conversion is always
-     * permissible.
+     * Determines whether a type can be converted to another without losing any
+     * precision. As a special case, void is considered convertible only to void
+     * and {@link Object} (either as {@code null} or as a custom value set in
+     * {@link DynamicLinkerFactory#setAutoConversionStrategy(MethodTypeConversionStrategy)}).
+     * Somewhat unintuitively, we consider anything to be convertible to void
+     * even though converting to void causes the ultimate loss of data. On the
+     * other hand, conversion to void essentially means that the value is of no
+     * interest and should be discarded, thus there's no expectation of
+     * preserving any precision.
      *
      * @param sourceType the source type
      * @param targetType the target type
@@ -284,9 +196,7 @@
             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));
+            return isBoxingAndWideningReferenceConversion(sourceType, targetType);
         }
         // 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.
@@ -294,51 +204,11 @@
     }
 
     /**
-     * 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.
-     *
-     * @param callSiteType the parameter type at the call site
-     * @param methodType the parameter type in the method declaration
-     * @return true if callSiteType is potentially convertible to the methodType.
-     */
-    public static boolean isPotentiallyConvertible(final Class<?> callSiteType, final Class<?> methodType) {
-        // Widening or narrowing reference conversion
-        if(areAssignable(callSiteType, methodType)) {
-            return true;
-        }
-        if(callSiteType.isPrimitive()) {
-            // Allow any conversion among primitives, as well as from any
-            // primitive to any type that can receive a boxed primitive.
-            // TODO: narrow this a bit, i.e. allow, say, boolean to Character?
-            // MethodHandles.convertArguments() allows it, so we might need to
-            // too.
-            return methodType.isPrimitive() || isAssignableFromBoxedPrimitive(methodType);
-        }
-        if(methodType.isPrimitive()) {
-            // Allow conversion from any reference type that can contain a
-            // boxed primitive to any primitive.
-            // TODO: narrow this a bit too?
-            return isAssignableFromBoxedPrimitive(callSiteType);
-        }
-        return false;
-    }
-
-    /**
-     * 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
-     * 5.1.5).
+     * 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 5.1.5).
      *
      * @param subType the supposed subtype
      * @param superType the supposed supertype of the subtype
@@ -432,82 +302,31 @@
         return false;
     }
 
-    private static final Map<Class<?>, Class<?>> WRAPPER_TO_PRIMITIVE_TYPES = createWrapperToPrimitiveTypes();
-
-    private static Map<Class<?>, Class<?>> createWrapperToPrimitiveTypes() {
-        final Map<Class<?>, Class<?>> classes = new IdentityHashMap<>();
-        classes.put(Void.class, Void.TYPE);
-        classes.put(Boolean.class, Boolean.TYPE);
-        classes.put(Byte.class, Byte.TYPE);
-        classes.put(Character.class, Character.TYPE);
-        classes.put(Short.class, Short.TYPE);
-        classes.put(Integer.class, Integer.TYPE);
-        classes.put(Long.class, Long.TYPE);
-        classes.put(Float.class, Float.TYPE);
-        classes.put(Double.class, Double.TYPE);
-        return classes;
-    }
-
-    private static final Set<Class<?>> PRIMITIVE_WRAPPER_TYPES = createPrimitiveWrapperTypes();
-
-    private static Set<Class<?>> createPrimitiveWrapperTypes() {
-        final Map<Class<?>, Class<?>> classes = new IdentityHashMap<>();
-        addClassHierarchy(classes, Boolean.class);
-        addClassHierarchy(classes, Byte.class);
-        addClassHierarchy(classes, Character.class);
-        addClassHierarchy(classes, Short.class);
-        addClassHierarchy(classes, Integer.class);
-        addClassHierarchy(classes, Long.class);
-        addClassHierarchy(classes, Float.class);
-        addClassHierarchy(classes, Double.class);
-        return classes.keySet();
-    }
-
-    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(final Class<?> itf: clazz.getInterfaces()) {
-            addClassHierarchy(map, itf);
-        }
-    }
-
     /**
-     * Returns true if the class can be assigned from any boxed primitive.
-     *
-     * @param clazz the class
-     * @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(final Class<?> clazz) {
-        return PRIMITIVE_WRAPPER_TYPES.contains(clazz);
-    }
-
-    /**
-     * Given a name of a primitive type (except "void"), returns the class representing it. I.e. when invoked with
-     * "int", returns {@link Integer#TYPE}.
+     * Given a name of a primitive type returns the class representing it. I.e.
+     * when invoked with "int", returns {@link Integer#TYPE}.
      * @param name the name of the primitive type
-     * @return the class representing the primitive type, or null if the name does not correspond to a primitive type
-     * or is "void".
+     * @return the class representing the primitive type, or null if the name
+     * does not correspond to a primitive type.
      */
     public static Class<?> getPrimitiveTypeByName(final String name) {
         return PRIMITIVE_TYPES_BY_NAME.get(name);
     }
 
     /**
-     * When passed a class representing a wrapper for a primitive type, returns the class representing the corresponding
-     * primitive type. I.e. calling it with {@code Integer.class} will return {@code Integer.TYPE}. If passed a class
-     * that is not a wrapper for primitive type, returns null.
-     * @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.
+     * When passed a class representing a wrapper for a primitive type, returns
+     * the class representing the corresponding primitive type. I.e. calling it
+     * with {@code Integer.class} will return {@code Integer.TYPE}. If passed a
+     * class that is not a wrapper for primitive type, returns null.
+     * @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(final Class<?> wrapperType) {
-        return WRAPPER_TO_PRIMITIVE_TYPES.get(wrapperType);
+        return PRIMITIVE_TYPES.get(wrapperType);
     }
 
-
     /**
      * When passed a class representing a primitive type, returns the class representing the corresponding
      * wrapper type. I.e. calling it with {@code int.class} will return {@code Integer.class}. If passed a class