1 /* 2 * Copyright (c) 2008, 2025, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package java.lang.invoke; 27 28 import java.lang.constant.ClassDesc; 29 import java.lang.constant.MethodTypeDesc; 30 import java.lang.invoke.MethodHandles.Lookup; 31 import java.lang.module.ModuleDescriptor; 32 import java.lang.ref.WeakReference; 33 import java.lang.reflect.Method; 34 import java.lang.reflect.Modifier; 35 import java.lang.reflect.UndeclaredThrowableException; 36 import java.util.ArrayList; 37 import java.util.Arrays; 38 import java.util.HashSet; 39 import java.util.List; 40 import java.util.Objects; 41 import java.util.Set; 42 import java.util.concurrent.atomic.AtomicInteger; 43 import java.util.stream.Stream; 44 45 import jdk.internal.access.JavaLangReflectAccess; 46 import jdk.internal.access.SharedSecrets; 47 import java.lang.classfile.ClassHierarchyResolver; 48 import java.lang.classfile.ClassFile; 49 import java.lang.classfile.CodeBuilder; 50 import java.lang.classfile.TypeKind; 51 52 import jdk.internal.constant.ConstantUtils; 53 import jdk.internal.loader.ClassLoaders; 54 import jdk.internal.module.Modules; 55 import jdk.internal.util.ClassFileDumper; 56 import jdk.internal.util.ReferencedKeySet; 57 58 import static java.lang.constant.ConstantDescs.*; 59 import static java.lang.invoke.MethodHandleStatics.*; 60 import static java.lang.invoke.MethodType.methodType; 61 import static java.lang.module.ModuleDescriptor.Modifier.SYNTHETIC; 62 import static java.lang.classfile.ClassFile.*; 63 import static jdk.internal.constant.ConstantUtils.*; 64 65 /** 66 * This class consists exclusively of static methods that help adapt 67 * method handles to other JVM types, such as interfaces. 68 * 69 * @since 1.7 70 */ 71 public final class MethodHandleProxies { 72 73 private MethodHandleProxies() { } // do not instantiate 74 75 /** 76 * Produces an instance of the given single-method interface which redirects 77 * its calls to the given method handle. 78 * <p> 79 * A single-method interface is an interface which declares a uniquely named method. 80 * When determining the uniquely named method of a single-method interface, 81 * the public {@code Object} methods ({@code toString}, {@code equals}, {@code hashCode}) 82 * are disregarded as are any default (non-abstract) methods. 83 * For example, {@link java.util.Comparator} is a single-method interface, 84 * even though it re-declares the {@code Object.equals} method and also 85 * declares default methods, such as {@code Comparator.reverse}. 86 * <p> 87 * The interface must be public, not {@linkplain Class#isHidden() hidden}, 88 * and not {@linkplain Class#isSealed() sealed}. 89 * No additional access checks are performed. 90 * <p> 91 * The resulting instance of the required type will respond to 92 * invocation of the type's uniquely named method by calling 93 * the given target on the incoming arguments, 94 * and returning or throwing whatever the target 95 * returns or throws. The invocation will be as if by 96 * {@code target.invoke}. 97 * The target's type will be checked before the 98 * instance is created, as if by a call to {@code asType}, 99 * which may result in a {@code WrongMethodTypeException}. 100 * <p> 101 * The uniquely named method is allowed to be multiply declared, 102 * with distinct type descriptors. (E.g., it can be overloaded, 103 * or can possess bridge methods.) All such declarations are 104 * connected directly to the target method handle. 105 * Argument and return types are adjusted by {@code asType} 106 * for each individual declaration. 107 * <p> 108 * The wrapper instance will implement the requested interface 109 * and its super-types, but no other single-method interfaces. 110 * This means that the instance will not unexpectedly 111 * pass an {@code instanceof} test for any unrequested type. 112 * <p style="font-size:smaller;"> 113 * <em>Implementation Note:</em> 114 * Therefore, each instance must implement a unique single-method interface. 115 * Implementations may not bundle together 116 * multiple single-method interfaces onto single implementation classes 117 * in the style of {@link java.desktop/java.awt.AWTEventMulticaster}. 118 * <p> 119 * The method handle may throw an <em>undeclared exception</em>, 120 * which means any checked exception (or other checked throwable) 121 * not declared by the requested type's single abstract method. 122 * If this happens, the throwable will be wrapped in an instance of 123 * {@link java.lang.reflect.UndeclaredThrowableException UndeclaredThrowableException} 124 * and thrown in that wrapped form. 125 * <p> 126 * Like {@link java.lang.Integer#valueOf Integer.valueOf}, 127 * {@code asInterfaceInstance} is a factory method whose results are defined 128 * by their behavior. 129 * It is not guaranteed to return a new instance for every call. 130 * <p> 131 * Because of the possibility of {@linkplain java.lang.reflect.Method#isBridge bridge methods} 132 * and other corner cases, the interface may also have several abstract methods 133 * with the same name but having distinct descriptors (types of returns and parameters). 134 * In this case, all the methods are bound in common to the one given target. 135 * The type check and effective {@code asType} conversion is applied to each 136 * method type descriptor, and all abstract methods are bound to the target in common. 137 * Beyond this type check, no further checks are made to determine that the 138 * abstract methods are related in any way. 139 * <p> 140 * Future versions of this API may accept additional types, 141 * such as abstract classes with single abstract methods. 142 * Future versions of this API may also equip wrapper instances 143 * with one or more additional public "marker" interfaces. 144 * 145 * @param <T> the desired type of the wrapper, a single-method interface 146 * @param intfc a class object representing {@code T} 147 * @param target the method handle to invoke from the wrapper 148 * @return a correctly-typed wrapper for the given target 149 * @throws NullPointerException if either argument is null 150 * @throws IllegalArgumentException if the {@code intfc} is not a 151 * valid argument to this method 152 * @throws WrongMethodTypeException if the target cannot 153 * be converted to the type required by the requested interface 154 */ 155 @SuppressWarnings("doclint:reference") // cross-module links 156 public static <T> T asInterfaceInstance(final Class<T> intfc, final MethodHandle target) { 157 if (!intfc.isInterface() || !Modifier.isPublic(intfc.getModifiers())) 158 throw newIllegalArgumentException("not a public interface", intfc.getName()); 159 if (intfc.isSealed()) 160 throw newIllegalArgumentException("a sealed interface", intfc.getName()); 161 if (intfc.isHidden()) 162 throw newIllegalArgumentException("a hidden interface", intfc.getName()); 163 Objects.requireNonNull(target); 164 final MethodHandle mh = target; 165 166 // Define one hidden class for each interface. Create an instance of 167 // the hidden class for a given target method handle which will be 168 // accessed via getfield. Multiple instances may be created for a 169 // hidden class. This approach allows the generated hidden classes 170 // more shareable. 171 // 172 // The implementation class is weakly referenced; a new class is 173 // defined if the last one has been garbage collected. 174 // 175 // An alternative approach is to define one hidden class with the 176 // target method handle as class data and the target method handle 177 // is loaded via ldc/condy. If more than one target method handles 178 // are used, the extra classes will pollute the same type profiles. 179 // In addition, hidden classes without class data is more friendly 180 // for pre-generation (shifting the dynamic class generation from 181 // runtime to an earlier phrase). 182 Class<?> proxyClass = getProxyClass(intfc); // throws IllegalArgumentException 183 Lookup lookup = new Lookup(proxyClass); 184 Object proxy; 185 try { 186 MethodHandle constructor = lookup.findConstructor(proxyClass, 187 MT_void_Lookup_MethodHandle_MethodHandle) 188 .asType(MT_Object_Lookup_MethodHandle_MethodHandle); 189 proxy = constructor.invokeExact(lookup, target, mh); 190 } catch (Throwable ex) { 191 throw uncaughtException(ex); 192 } 193 assert proxy.getClass().getModule().isNamed() : proxy.getClass() + " " + proxy.getClass().getModule(); 194 return intfc.cast(proxy); 195 } 196 197 private record MethodInfo(MethodTypeDesc desc, List<ClassDesc> thrown, String fieldName) {} 198 199 private static final ClassFileDumper DUMPER = ClassFileDumper.getInstance( 200 "jdk.invoke.MethodHandleProxies.dumpClassFiles", "DUMP_MH_PROXY_CLASSFILES"); 201 202 private static final Set<Class<?>> WRAPPER_TYPES = ReferencedKeySet.create(false, ReferencedKeySet.concurrentHashMapSupplier()); 203 private static final ClassValue<WeakReferenceHolder<Class<?>>> PROXIES = new ClassValue<>() { 204 @Override 205 protected WeakReferenceHolder<Class<?>> computeValue(Class<?> intfc) { 206 return new WeakReferenceHolder<>(newProxyClass(intfc)); 207 } 208 }; 209 210 private static Class<?> newProxyClass(Class<?> intfc) { 211 List<MethodInfo> methods = new ArrayList<>(); 212 Set<Class<?>> referencedTypes = new HashSet<>(); 213 referencedTypes.add(intfc); 214 String uniqueName = null; 215 int count = 0; 216 for (Method m : intfc.getMethods()) { 217 if (!Modifier.isAbstract(m.getModifiers())) 218 continue; 219 220 if (isObjectMethod(m)) 221 continue; 222 223 // ensure it's SAM interface 224 String methodName = m.getName(); 225 if (uniqueName == null) { 226 uniqueName = methodName; 227 } else if (!uniqueName.equals(methodName)) { 228 // too many abstract methods 229 throw newIllegalArgumentException("not a single-method interface", intfc.getName()); 230 } 231 232 // the field name holding the method handle for this method 233 String fieldName = "m" + count++; 234 var md = methodTypeDesc(m.getReturnType(), JLRA.getExecutableSharedParameterTypes(m)); 235 var thrown = JLRA.getExecutableSharedExceptionTypes(m); 236 var exceptionTypeDescs = 237 thrown.length == 0 ? DEFAULT_RETHROWS 238 : Stream.concat(DEFAULT_RETHROWS.stream(), 239 Arrays.stream(thrown).map(ConstantUtils::referenceClassDesc)) 240 .distinct().toList(); 241 methods.add(new MethodInfo(md, exceptionTypeDescs, fieldName)); 242 243 // find the types referenced by this method 244 addElementType(referencedTypes, m.getReturnType()); 245 addElementTypes(referencedTypes, JLRA.getExecutableSharedParameterTypes(m)); 246 addElementTypes(referencedTypes, JLRA.getExecutableSharedExceptionTypes(m)); 247 } 248 249 if (uniqueName == null) 250 throw newIllegalArgumentException("no method in ", intfc.getName()); 251 252 // create a dynamic module for each proxy class, which needs access 253 // to the types referenced by the members of the interface including 254 // the parameter types, return type and exception types 255 var loader = intfc.getClassLoader(); 256 Module targetModule = newDynamicModule(loader, referencedTypes); 257 258 // generate a class file in the package of the dynamic module 259 String packageName = targetModule.getName(); 260 String intfcName = intfc.getName(); 261 int i = intfcName.lastIndexOf('.'); 262 // jdk.MHProxy#.Interface 263 String className = packageName + "." + (i > 0 ? intfcName.substring(i + 1) : intfcName); 264 byte[] template = createTemplate(loader, binaryNameToDesc(className), 265 referenceClassDesc(intfc), uniqueName, methods); 266 // define the dynamic module to the class loader of the interface 267 var definer = new Lookup(intfc).makeHiddenClassDefiner(className, template, DUMPER); 268 269 Lookup lookup = definer.defineClassAsLookup(true); 270 // cache the wrapper type 271 var ret = lookup.lookupClass(); 272 WRAPPER_TYPES.add(ret); 273 return ret; 274 } 275 276 private static final class WeakReferenceHolder<T> { 277 private volatile WeakReference<T> ref; 278 279 WeakReferenceHolder(T value) { 280 set(value); 281 } 282 283 void set(T value) { 284 ref = new WeakReference<>(value); 285 } 286 287 T get() { 288 return ref.get(); 289 } 290 } 291 292 private static Class<?> getProxyClass(Class<?> intfc) { 293 WeakReferenceHolder<Class<?>> r = PROXIES.get(intfc); 294 Class<?> cl = r.get(); 295 if (cl != null) 296 return cl; 297 298 // avoid spinning multiple classes in a race 299 synchronized (r) { 300 cl = r.get(); 301 if (cl != null) 302 return cl; 303 304 // If the referent is cleared, create a new value and update cached weak reference. 305 cl = newProxyClass(intfc); 306 r.set(cl); 307 return cl; 308 } 309 } 310 311 private static final List<ClassDesc> DEFAULT_RETHROWS = List.of(referenceClassDesc(RuntimeException.class), referenceClassDesc(Error.class)); 312 private static final ClassDesc CD_UndeclaredThrowableException = referenceClassDesc(UndeclaredThrowableException.class); 313 private static final ClassDesc CD_IllegalAccessException = referenceClassDesc(IllegalAccessException.class); 314 private static final MethodTypeDesc MTD_void_Throwable = MethodTypeDesc.of(CD_void, CD_Throwable); 315 private static final MethodType MT_void_Lookup_MethodHandle_MethodHandle = 316 methodType(void.class, Lookup.class, MethodHandle.class, MethodHandle.class); 317 private static final MethodType MT_Object_Lookup_MethodHandle_MethodHandle = 318 MT_void_Lookup_MethodHandle_MethodHandle.changeReturnType(Object.class); 319 private static final MethodType MT_MethodHandle_Object = methodType(MethodHandle.class, Object.class); 320 private static final MethodTypeDesc MTD_void_Lookup_MethodHandle_MethodHandle 321 = methodTypeDesc(MT_void_Lookup_MethodHandle_MethodHandle); 322 private static final MethodTypeDesc MTD_void_Lookup = MethodTypeDesc.of(CD_void, CD_MethodHandles_Lookup); 323 private static final MethodTypeDesc MTD_MethodHandle_MethodType = MethodTypeDesc.of(CD_MethodHandle, CD_MethodType); 324 private static final MethodTypeDesc MTD_Class = MethodTypeDesc.of(CD_Class); 325 private static final MethodTypeDesc MTD_int = MethodTypeDesc.of(CD_int); 326 private static final MethodTypeDesc MTD_String = MethodTypeDesc.of(CD_String); 327 private static final MethodTypeDesc MTD_void_String = MethodTypeDesc.of(CD_void, CD_String); 328 private static final String TARGET_NAME = "target"; 329 private static final String TYPE_NAME = "interfaceType"; 330 private static final String ENSURE_ORIGINAL_LOOKUP = "ensureOriginalLookup"; 331 332 /** 333 * Creates an implementation class file for a given interface. One implementation class is 334 * defined for each interface. 335 * 336 * @param ifaceDesc the given interface 337 * @param methodName the name of the single abstract method 338 * @param methods the information for implementation methods 339 * @return the bytes of the implementation classes 340 */ 341 private static byte[] createTemplate(ClassLoader loader, ClassDesc proxyDesc, ClassDesc ifaceDesc, 342 String methodName, List<MethodInfo> methods) { 343 return ClassFile.of(ClassHierarchyResolverOption.of(ClassHierarchyResolver.ofClassLoading(loader == null ? 344 ClassLoaders.platformClassLoader() : loader))) 345 .build(proxyDesc, clb -> { 346 clb.withSuperclass(CD_Object) 347 .withFlags(ACC_FINAL | ACC_SYNTHETIC) 348 .withInterfaceSymbols(ifaceDesc) 349 // static and instance fields 350 .withField(TYPE_NAME, CD_Class, ACC_PRIVATE | ACC_STATIC | ACC_FINAL) 351 .withField(TARGET_NAME, CD_MethodHandle, ACC_PRIVATE | ACC_FINAL); 352 for (var mi : methods) { 353 clb.withField(mi.fieldName, CD_MethodHandle, ACC_PRIVATE | ACC_FINAL); 354 } 355 356 // <clinit> 357 clb.withMethodBody(CLASS_INIT_NAME, MTD_void, ACC_STATIC, cob -> { 358 cob.loadConstant(ifaceDesc) 359 .putstatic(proxyDesc, TYPE_NAME, CD_Class) 360 .return_(); 361 }); 362 363 // <init>(Lookup, MethodHandle target, MethodHandle callerBoundTarget) 364 clb.withMethodBody(INIT_NAME, MTD_void_Lookup_MethodHandle_MethodHandle, 0, cob -> { 365 cob.aload(0) 366 .invokespecial(CD_Object, INIT_NAME, MTD_void) 367 // call ensureOriginalLookup to verify the given Lookup has access 368 .aload(1) 369 .invokestatic(proxyDesc, ENSURE_ORIGINAL_LOOKUP, MTD_void_Lookup) 370 // this.target = target; 371 .aload(0) 372 .aload(2) 373 .putfield(proxyDesc, TARGET_NAME, CD_MethodHandle); 374 375 // method handles adjusted to the method type of each method 376 for (var mi : methods) { 377 // this.m<i> = callerBoundTarget.asType(xxType); 378 cob.aload(0) 379 .aload(3) 380 .loadConstant(mi.desc) 381 .invokevirtual(CD_MethodHandle, "asType", MTD_MethodHandle_MethodType) 382 .putfield(proxyDesc, mi.fieldName, CD_MethodHandle); 383 } 384 385 // complete 386 cob.return_(); 387 }); 388 389 // private static void ensureOriginalLookup(Lookup) checks if the given Lookup 390 // has ORIGINAL access to this class, i.e. the lookup class is this class; 391 // otherwise, IllegalAccessException is thrown 392 clb.withMethodBody(ENSURE_ORIGINAL_LOOKUP, MTD_void_Lookup, ACC_PRIVATE | ACC_STATIC, cob -> { 393 var failLabel = cob.newLabel(); 394 // check lookupClass 395 cob.aload(0) 396 .invokevirtual(CD_MethodHandles_Lookup, "lookupClass", MTD_Class) 397 .loadConstant(proxyDesc) 398 .if_acmpne(failLabel) 399 // check original access 400 .aload(0) 401 .invokevirtual(CD_MethodHandles_Lookup, "lookupModes", MTD_int) 402 .loadConstant(Lookup.ORIGINAL) 403 .iand() 404 .ifeq(failLabel) 405 // success 406 .return_() 407 // throw exception 408 .labelBinding(failLabel) 409 .new_(CD_IllegalAccessException) 410 .dup() 411 .aload(0) // lookup 412 .invokevirtual(CD_Object, "toString", MTD_String) 413 .invokespecial(CD_IllegalAccessException, INIT_NAME, MTD_void_String) 414 .athrow(); 415 }); 416 417 // implementation methods 418 for (MethodInfo mi : methods) { 419 // no need to generate thrown exception attribute 420 clb.withMethodBody(methodName, mi.desc, ACC_PUBLIC, cob -> cob 421 .trying(bcb -> { 422 // return this.handleField.invokeExact(arguments...); 423 bcb.aload(0) 424 .getfield(proxyDesc, mi.fieldName, CD_MethodHandle); 425 for (int j = 0; j < mi.desc.parameterCount(); j++) { 426 bcb.loadLocal(TypeKind.from(mi.desc.parameterType(j)), 427 bcb.parameterSlot(j)); 428 } 429 bcb.invokevirtual(CD_MethodHandle, "invokeExact", mi.desc) 430 .return_(TypeKind.from(mi.desc.returnType())); 431 }, ctb -> ctb 432 // catch (Error | RuntimeException | Declared ex) { throw ex; } 433 .catchingMulti(mi.thrown, CodeBuilder::athrow) 434 // catch (Throwable ex) { throw new UndeclaredThrowableException(ex); } 435 .catchingAll(cb -> cb 436 .new_(CD_UndeclaredThrowableException) 437 .dup_x1() 438 .swap() 439 .invokespecial(CD_UndeclaredThrowableException, 440 INIT_NAME, MTD_void_Throwable) 441 .athrow() 442 ) 443 )); 444 } 445 }); 446 } 447 448 private static MethodHandle bindCaller(MethodHandle target, Class<?> hostClass) { 449 return MethodHandleImpl.bindCaller(target, hostClass).withVarargs(target.isVarargsCollector()); 450 } 451 452 /** 453 * Determines if the given object was produced by a call to {@link #asInterfaceInstance asInterfaceInstance}. 454 * @param x any reference 455 * @return true if the reference is not null and points to an object produced by {@code asInterfaceInstance} 456 */ 457 public static boolean isWrapperInstance(Object x) { 458 return x != null && WRAPPER_TYPES.contains(x.getClass()); 459 } 460 461 /** 462 * Produces or recovers a target method handle which is behaviorally 463 * equivalent to the unique method of this wrapper instance. 464 * The object {@code x} must have been produced by a call to {@link #asInterfaceInstance asInterfaceInstance}. 465 * This requirement may be tested via {@link #isWrapperInstance isWrapperInstance}. 466 * @param x any reference 467 * @return a method handle implementing the unique method 468 * @throws IllegalArgumentException if the reference x is not to a wrapper instance 469 */ 470 public static MethodHandle wrapperInstanceTarget(Object x) { 471 if (!isWrapperInstance(x)) 472 throw new IllegalArgumentException("not a wrapper instance: " + x); 473 474 try { 475 Class<?> type = x.getClass(); 476 MethodHandle getter = new Lookup(type).findGetter(type, TARGET_NAME, MethodHandle.class) 477 .asType(MT_MethodHandle_Object); 478 return (MethodHandle) getter.invokeExact(x); 479 } catch (Throwable ex) { 480 throw uncaughtException(ex); 481 } 482 } 483 484 /** 485 * Recovers the unique single-method interface type for which this wrapper instance was created. 486 * The object {@code x} must have been produced by a call to {@link #asInterfaceInstance asInterfaceInstance}. 487 * This requirement may be tested via {@link #isWrapperInstance isWrapperInstance}. 488 * @param x any reference 489 * @return the single-method interface type for which the wrapper was created 490 * @throws IllegalArgumentException if the reference x is not to a wrapper instance 491 */ 492 public static Class<?> wrapperInstanceType(Object x) { 493 if (!isWrapperInstance(x)) 494 throw new IllegalArgumentException("not a wrapper instance: " + x); 495 496 try { 497 Class<?> type = x.getClass(); 498 MethodHandle originalTypeField = new Lookup(type).findStaticGetter(type, TYPE_NAME, Class.class); 499 return (Class<?>) originalTypeField.invokeExact(); 500 } catch (Throwable e) { 501 throw uncaughtException(e); 502 } 503 } 504 505 private static final JavaLangReflectAccess JLRA = SharedSecrets.getJavaLangReflectAccess(); 506 private static final AtomicInteger counter = new AtomicInteger(); 507 508 private static String nextModuleName() { 509 return "jdk.MHProxy" + counter.incrementAndGet(); 510 } 511 512 /** 513 * Create a dynamic module defined to the given class loader and has 514 * access to the given types. 515 * <p> 516 * The dynamic module contains only one single package named the same as 517 * the name of the dynamic module. It's not exported or open. 518 */ 519 private static Module newDynamicModule(ClassLoader ld, Set<Class<?>> types) { 520 Objects.requireNonNull(types); 521 522 // create a dynamic module and setup module access 523 String mn = nextModuleName(); 524 ModuleDescriptor descriptor = ModuleDescriptor.newModule(mn, Set.of(SYNTHETIC)) 525 .packages(Set.of(mn)) 526 .build(); 527 528 Module dynModule = Modules.defineModule(ld, descriptor, null); 529 Module javaBase = Object.class.getModule(); 530 531 Modules.addReads(dynModule, javaBase); 532 Modules.addOpens(dynModule, mn, javaBase); 533 534 for (Class<?> c : types) { 535 ensureAccess(dynModule, c); 536 } 537 return dynModule; 538 } 539 540 private static boolean isObjectMethod(Method m) { 541 return switch (m.getName()) { 542 case "toString" -> m.getReturnType() == String.class 543 && m.getParameterCount() == 0; 544 case "hashCode" -> m.getReturnType() == int.class 545 && m.getParameterCount() == 0; 546 case "equals" -> m.getReturnType() == boolean.class 547 && m.getParameterCount() == 1 548 && JLRA.getExecutableSharedParameterTypes(m)[0] == Object.class; 549 default -> false; 550 }; 551 } 552 553 /* 554 * Ensure the given module can access the given class. 555 */ 556 private static void ensureAccess(Module target, Class<?> c) { 557 Module m = c.getModule(); 558 // add read edge and qualified export for the target module to access 559 if (!target.canRead(m)) { 560 Modules.addReads(target, m); 561 } 562 String pn = c.getPackageName(); 563 if (!m.isExported(pn, target)) { 564 Modules.addExports(m, pn, target); 565 } 566 } 567 568 private static void addElementTypes(Set<Class<?>> types, Class<?>... classes) { 569 for (var cls : classes) { 570 addElementType(types, cls); 571 } 572 } 573 574 private static void addElementType(Set<Class<?>> types, Class<?> cls) { 575 Class<?> e = cls; 576 while (e.isArray()) { 577 e = e.getComponentType(); 578 } 579 580 if (!e.isPrimitive()) { 581 types.add(e); 582 } 583 } 584 }