|
11 | 11 | package org.junit.platform.commons.util;
|
12 | 12 |
|
13 | 13 | import static java.lang.String.format;
|
| 14 | +import static java.util.Collections.synchronizedMap; |
14 | 15 | import static java.util.stream.Collectors.toCollection;
|
15 | 16 | import static java.util.stream.Collectors.toList;
|
16 | 17 | import static java.util.stream.Collectors.toSet;
|
@@ -125,6 +126,13 @@ public enum HierarchyTraversalMode {
|
125 | 126 | private static final ClasspathScanner classpathScanner = new ClasspathScanner(
|
126 | 127 | ClassLoaderUtils::getDefaultClassLoader, ReflectionUtils::tryToLoadClass);
|
127 | 128 |
|
| 129 | + /** |
| 130 | + * Cache for equivalent methods on an interface implemented by the declaring class. |
| 131 | + * @since 1.11 |
| 132 | + * @see #getInterfaceMethodIfPossible(Method, Class) |
| 133 | + */ |
| 134 | + private static final Map<Method, Method> interfaceMethodCache = synchronizedMap(new LruCache<>(255)); |
| 135 | + |
128 | 136 | /**
|
129 | 137 | * Set of fully qualified class names for which no cycles have been detected
|
130 | 138 | * in inner class hierarchies.
|
@@ -1312,6 +1320,54 @@ public static Try<Method> tryToGetMethod(Class<?> clazz, String methodName, Clas
|
1312 | 1320 | return Try.call(() -> clazz.getMethod(methodName, parameterTypes));
|
1313 | 1321 | }
|
1314 | 1322 |
|
| 1323 | + /** |
| 1324 | + * Determine a corresponding interface method for the given method handle, if possible. |
| 1325 | + * <p>This is particularly useful for arriving at a public exported type on the Java |
| 1326 | + * Module System which can be reflectively invoked without an illegal access warning. |
| 1327 | + * @param method the method to be invoked, potentially from an implementation class |
| 1328 | + * @param targetClass the target class to check for declared interfaces |
| 1329 | + * @return the corresponding interface method, or the original method if none found |
| 1330 | + * @since 1.11 |
| 1331 | + */ |
| 1332 | + @API(status = INTERNAL, since = "1.11") |
| 1333 | + public static Method getInterfaceMethodIfPossible(Method method, Class<?> targetClass) { |
| 1334 | + if (!isPublic(method) || method.getDeclaringClass().isInterface()) { |
| 1335 | + return method; |
| 1336 | + } |
| 1337 | + // Try cached version of method in its declaring class |
| 1338 | + Method result = interfaceMethodCache.computeIfAbsent(method, |
| 1339 | + m -> findInterfaceMethodIfPossible(m, m.getDeclaringClass(), Object.class)); |
| 1340 | + if (result == method && targetClass != null) { |
| 1341 | + // No interface method found yet -> try given target class (possibly a subclass of the |
| 1342 | + // declaring class, late-binding a base class method to a subclass-declared interface: |
| 1343 | + // see e.g. HashMap.HashIterator.hasNext) |
| 1344 | + result = findInterfaceMethodIfPossible(method, targetClass, method.getDeclaringClass()); |
| 1345 | + } |
| 1346 | + return result; |
| 1347 | + } |
| 1348 | + |
| 1349 | + private static Method findInterfaceMethodIfPossible(Method method, Class<?> startClass, Class<?> endClass) { |
| 1350 | + Class<?>[] parameterTypes = null; |
| 1351 | + Class<?> current = startClass; |
| 1352 | + while (current != null && current != endClass) { |
| 1353 | + if (parameterTypes == null) { |
| 1354 | + // Since Method#getParameterTypes() clones the array, we lazily retrieve |
| 1355 | + // and cache parameter types to avoid cloning the array multiple times. |
| 1356 | + parameterTypes = method.getParameterTypes(); |
| 1357 | + } |
| 1358 | + for (Class<?> ifc : current.getInterfaces()) { |
| 1359 | + try { |
| 1360 | + return ifc.getMethod(method.getName(), parameterTypes); |
| 1361 | + } |
| 1362 | + catch (NoSuchMethodException ex) { |
| 1363 | + // ignore |
| 1364 | + } |
| 1365 | + } |
| 1366 | + current = current.getSuperclass(); |
| 1367 | + } |
| 1368 | + return method; |
| 1369 | + } |
| 1370 | + |
1315 | 1371 | /**
|
1316 | 1372 | * @see org.junit.platform.commons.support.ReflectionSupport#findMethod(Class, String, String)
|
1317 | 1373 | */
|
|
0 commit comments