|
18 | 18 |
|
19 | 19 | import java.lang.reflect.Constructor;
|
20 | 20 | import java.lang.reflect.Method;
|
| 21 | +import java.lang.reflect.Modifier; |
21 | 22 | import java.util.ArrayDeque;
|
22 | 23 | import java.util.ArrayList;
|
23 | 24 | import java.util.Deque;
|
24 | 25 | import java.util.List;
|
| 26 | +import java.util.Map; |
25 | 27 |
|
26 | 28 | import org.springframework.asm.ClassWriter;
|
27 | 29 | import org.springframework.asm.MethodVisitor;
|
28 | 30 | import org.springframework.asm.Opcodes;
|
29 | 31 | import org.springframework.lang.Nullable;
|
| 32 | +import org.springframework.util.ClassUtils; |
30 | 33 | import org.springframework.util.CollectionUtils;
|
| 34 | +import org.springframework.util.ConcurrentReferenceHashMap; |
31 | 35 |
|
32 | 36 | /**
|
33 | 37 | * Manages the class being generated by the compilation process.
|
34 | 38 | *
|
35 | 39 | * <p>Records intermediate compilation state as the bytecode is generated.
|
36 |
| - * Also includes various bytecode generation helper functions. |
| 40 | + * |
| 41 | + * <p>Also includes various bytecode generation helper functions. |
37 | 42 | *
|
38 | 43 | * @author Andy Clement
|
39 | 44 | * @author Juergen Hoeller
|
| 45 | + * @author Sam Brannen |
40 | 46 | * @since 4.1
|
41 | 47 | */
|
42 | 48 | public class CodeFlow implements Opcodes {
|
43 | 49 |
|
| 50 | + /** |
| 51 | + * Cache for equivalent methods in a public declaring class in the type |
| 52 | + * hierarchy of the method's declaring class. |
| 53 | + * @since 6.2 |
| 54 | + */ |
| 55 | + private static final Map<Method, Class<?>> publicDeclaringClassCache = new ConcurrentReferenceHashMap<>(256); |
| 56 | + |
| 57 | + |
44 | 58 | /**
|
45 | 59 | * Name of the class being generated. Typically used when generating code
|
46 | 60 | * that accesses freshly generated fields on the generated type.
|
@@ -395,6 +409,65 @@ public static void insertAnyNecessaryTypeConversionBytecodes(MethodVisitor mv, c
|
395 | 409 | }
|
396 | 410 | }
|
397 | 411 |
|
| 412 | + /** |
| 413 | + * Find the first public class or interface in the method's class hierarchy |
| 414 | + * that declares the supplied method. |
| 415 | + * <p>Sometimes the reflective method discovery logic finds a suitable method |
| 416 | + * that can easily be called via reflection but cannot be called from generated |
| 417 | + * code when compiling the expression because of visibility restrictions. For |
| 418 | + * example, if a non-public class overrides {@code toString()}, this method |
| 419 | + * will traverse up the type hierarchy to find the first public type that |
| 420 | + * declares the method (if there is one). For {@code toString()}, it may |
| 421 | + * traverse as far as {@link Object}. |
| 422 | + * @param method the method to process |
| 423 | + * @return the public class or interface that declares the method, or |
| 424 | + * {@code null} if no such public type could be found |
| 425 | + * @since 6.2 |
| 426 | + */ |
| 427 | + @Nullable |
| 428 | + public static Class<?> findPublicDeclaringClass(Method method) { |
| 429 | + return publicDeclaringClassCache.computeIfAbsent(method, key -> { |
| 430 | + // If the method is already defined in a public type, return that type. |
| 431 | + if (Modifier.isPublic(key.getDeclaringClass().getModifiers())) { |
| 432 | + return key.getDeclaringClass(); |
| 433 | + } |
| 434 | + Method interfaceMethod = ClassUtils.getInterfaceMethodIfPossible(key, null); |
| 435 | + // If we found an interface method whose type is public, return the interface type. |
| 436 | + if (!interfaceMethod.equals(key)) { |
| 437 | + if (Modifier.isPublic(interfaceMethod.getDeclaringClass().getModifiers())) { |
| 438 | + return interfaceMethod.getDeclaringClass(); |
| 439 | + } |
| 440 | + } |
| 441 | + // Attempt to search the type hierarchy. |
| 442 | + Class<?> superclass = key.getDeclaringClass().getSuperclass(); |
| 443 | + if (superclass != null) { |
| 444 | + return findPublicDeclaringClass(superclass, key.getName(), key.getParameterTypes()); |
| 445 | + } |
| 446 | + // Otherwise, no public declaring class found. |
| 447 | + return null; |
| 448 | + }); |
| 449 | + } |
| 450 | + |
| 451 | + @Nullable |
| 452 | + private static Class<?> findPublicDeclaringClass( |
| 453 | + Class<?> declaringClass, String methodName, Class<?>[] parameterTypes) { |
| 454 | + |
| 455 | + if (Modifier.isPublic(declaringClass.getModifiers())) { |
| 456 | + try { |
| 457 | + declaringClass.getDeclaredMethod(methodName, parameterTypes); |
| 458 | + return declaringClass; |
| 459 | + } |
| 460 | + catch (NoSuchMethodException ex) { |
| 461 | + // Continue below... |
| 462 | + } |
| 463 | + } |
| 464 | + |
| 465 | + Class<?> superclass = declaringClass.getSuperclass(); |
| 466 | + if (superclass != null) { |
| 467 | + return findPublicDeclaringClass(superclass, methodName, parameterTypes); |
| 468 | + } |
| 469 | + return null; |
| 470 | + } |
398 | 471 |
|
399 | 472 | /**
|
400 | 473 | * Create the JVM signature descriptor for a method. This consists of the descriptors
|
|
0 commit comments