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