1 /*
2 * Copyright (c) 2017, 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.runtime;
27
28 import java.lang.classfile.ClassFile;
29 import java.lang.classfile.ClassHierarchyResolver;
30 import java.lang.classfile.Opcode;
31 import java.lang.constant.ClassDesc;
32 import java.lang.constant.MethodTypeDesc;
33 import java.lang.invoke.ConstantCallSite;
34 import java.lang.invoke.MethodHandle;
35 import java.lang.invoke.MethodHandles;
36 import java.lang.invoke.MethodType;
37 import java.lang.invoke.StringConcatFactory;
38 import java.lang.invoke.TypeDescriptor;
39 import java.lang.reflect.Modifier;
40 import java.util.ArrayList;
41 import java.util.Arrays;
42 import java.util.HashMap;
43 import java.util.List;
44 import java.util.Objects;
45
46 import static java.lang.classfile.ClassFile.ACC_STATIC;
47 import static java.lang.constant.ConstantDescs.*;
48 import static java.util.Objects.requireNonNull;
49
50 /**
51 * Bootstrap methods for state-driven implementations of core methods,
52 * including {@link Object#equals(Object)}, {@link Object#hashCode()}, and
53 * {@link Object#toString()}. These methods may be used, for example, by
54 * Java compiler implementations to implement the bodies of {@link Object}
55 * methods for record classes.
56 *
57 * @since 16
58 */
59 public final class ObjectMethods {
60
61 private ObjectMethods() { }
62
63 private static final int MAX_STRING_CONCAT_SLOTS = 20;
64
65 private static final MethodHandle FALSE = MethodHandles.zero(boolean.class);
66 private static final MethodHandle TRUE = MethodHandles.constant(boolean.class, true);
67 private static final MethodHandle ZERO = MethodHandles.zero(int.class);
68 private static final MethodHandle CLASS_IS_INSTANCE;
69 private static final MethodHandle IS_NULL;
70 private static final MethodHandle IS_ARG0_NULL;
71 private static final MethodHandle IS_ARG1_NULL;
72 private static final MethodHandle OBJECT_EQ;
73 private static final MethodHandle HASH_COMBINER;
74 private static final MethodType MT_OBJECT_BOOLEAN = MethodType.methodType(boolean.class, Object.class);
75 private static final MethodType MT_INT = MethodType.methodType(int.class);
76 private static final MethodTypeDesc MTD_OBJECT_BOOLEAN = MethodTypeDesc.of(CD_boolean, CD_Object);
77 private static final MethodTypeDesc MTD_INT = MethodTypeDesc.of(CD_int);
78
79 private static final HashMap<Class<?>, MethodHandle> primitiveEquals = new HashMap<>();
80 private static final HashMap<Class<?>, MethodHandle> primitiveHashers = new HashMap<>();
81
82 static {
83 try {
84 Class<ObjectMethods> OBJECT_METHODS_CLASS = ObjectMethods.class;
85 MethodHandles.Lookup publicLookup = MethodHandles.publicLookup();
86 MethodHandles.Lookup lookup = MethodHandles.lookup();
87
88 CLASS_IS_INSTANCE = publicLookup.findVirtual(Class.class, "isInstance",
89 MethodType.methodType(boolean.class, Object.class));
90
91 var objectsIsNull = publicLookup.findStatic(Objects.class, "isNull",
92 MethodType.methodType(boolean.class, Object.class));
93 IS_NULL = objectsIsNull;
94 IS_ARG0_NULL = MethodHandles.dropArguments(objectsIsNull, 1, Object.class);
95 IS_ARG1_NULL = MethodHandles.dropArguments(objectsIsNull, 0, Object.class);
96
97 OBJECT_EQ = lookup.findStatic(OBJECT_METHODS_CLASS, "eq",
98 MethodType.methodType(boolean.class, Object.class, Object.class));
99 HASH_COMBINER = lookup.findStatic(OBJECT_METHODS_CLASS, "hashCombiner",
100 MethodType.methodType(int.class, int.class, int.class));
101
102 primitiveEquals.put(byte.class, lookup.findStatic(OBJECT_METHODS_CLASS, "eq",
103 MethodType.methodType(boolean.class, byte.class, byte.class)));
104 primitiveEquals.put(short.class, lookup.findStatic(OBJECT_METHODS_CLASS, "eq",
105 MethodType.methodType(boolean.class, short.class, short.class)));
106 primitiveEquals.put(char.class, lookup.findStatic(OBJECT_METHODS_CLASS, "eq",
107 MethodType.methodType(boolean.class, char.class, char.class)));
108 primitiveEquals.put(int.class, lookup.findStatic(OBJECT_METHODS_CLASS, "eq",
109 MethodType.methodType(boolean.class, int.class, int.class)));
110 primitiveEquals.put(long.class, lookup.findStatic(OBJECT_METHODS_CLASS, "eq",
111 MethodType.methodType(boolean.class, long.class, long.class)));
112 primitiveEquals.put(float.class, lookup.findStatic(OBJECT_METHODS_CLASS, "eq",
113 MethodType.methodType(boolean.class, float.class, float.class)));
114 primitiveEquals.put(double.class, lookup.findStatic(OBJECT_METHODS_CLASS, "eq",
115 MethodType.methodType(boolean.class, double.class, double.class)));
116 primitiveEquals.put(boolean.class, lookup.findStatic(OBJECT_METHODS_CLASS, "eq",
117 MethodType.methodType(boolean.class, boolean.class, boolean.class)));
118
119 primitiveHashers.put(byte.class, lookup.findStatic(Byte.class, "hashCode",
120 MethodType.methodType(int.class, byte.class)));
121 primitiveHashers.put(short.class, lookup.findStatic(Short.class, "hashCode",
122 MethodType.methodType(int.class, short.class)));
123 primitiveHashers.put(char.class, lookup.findStatic(Character.class, "hashCode",
124 MethodType.methodType(int.class, char.class)));
125 primitiveHashers.put(int.class, lookup.findStatic(Integer.class, "hashCode",
126 MethodType.methodType(int.class, int.class)));
127 primitiveHashers.put(long.class, lookup.findStatic(Long.class, "hashCode",
128 MethodType.methodType(int.class, long.class)));
129 primitiveHashers.put(float.class, lookup.findStatic(Float.class, "hashCode",
130 MethodType.methodType(int.class, float.class)));
131 primitiveHashers.put(double.class, lookup.findStatic(Double.class, "hashCode",
132 MethodType.methodType(int.class, double.class)));
133 primitiveHashers.put(boolean.class, lookup.findStatic(Boolean.class, "hashCode",
134 MethodType.methodType(int.class, boolean.class)));
135 }
136 catch (ReflectiveOperationException e) {
137 throw new RuntimeException(e);
138 }
139 }
140
141 private static int hashCombiner(int x, int y) {
142 return x*31 + y;
143 }
144
145 private static boolean eq(Object a, Object b) { return a == b; }
146 private static boolean eq(byte a, byte b) { return a == b; }
147 private static boolean eq(short a, short b) { return a == b; }
148 private static boolean eq(char a, char b) { return a == b; }
149 private static boolean eq(int a, int b) { return a == b; }
150 private static boolean eq(long a, long b) { return a == b; }
151 private static boolean eq(float a, float b) { return Float.compare(a, b) == 0; }
152 private static boolean eq(double a, double b) { return Double.compare(a, b) == 0; }
153 private static boolean eq(boolean a, boolean b) { return a == b; }
154
155 /** Get the method handle for combining two values of a given type */
156 private static MethodHandle equalator(MethodHandles.Lookup lookup, Class<?> clazz) throws Throwable {
157 if (clazz.isPrimitive())
158 return primitiveEquals.get(clazz);
159 MethodType mt = MethodType.methodType(boolean.class, clazz, clazz);
160 return MethodHandles.guardWithTest(IS_ARG0_NULL.asType(mt),
161 IS_ARG1_NULL.asType(mt),
162 lookup.findVirtual(clazz, "equals", MT_OBJECT_BOOLEAN).asType(mt));
163 }
164
165 /** Get the hasher for a value of a given type */
166 private static MethodHandle hasher(MethodHandles.Lookup lookup, Class<?> clazz) throws Throwable {
167 if (clazz.isPrimitive())
168 return primitiveHashers.get(clazz);
169 MethodType mt = MethodType.methodType(int.class, clazz);
170 return MethodHandles.guardWithTest(IS_NULL.asType(MethodType.methodType(boolean.class, clazz)),
171 MethodHandles.dropArguments(MethodHandles.zero(int.class), 0, clazz),
172 lookup.findVirtual(clazz, "hashCode", MT_INT).asType(mt));
173 }
174
175 // If this type must be a monomorphic receiver, that is, one that has no
176 // subtypes in the JVM. For example, Object-typed fields may have a more
177 // specific one type at runtime and thus need optimizations.
178 private static boolean isMonomorphic(Class<?> type) {
179 // Includes primitives and final classes, but not arrays.
180 // All array classes are reported to be final, but Object[] can have subtypes like String[]
181 return Modifier.isFinal(type.getModifiers()) && !type.isArray();
182 }
183
184 private static String specializerClassName(Class<?> targetClass, String kind) {
185 String name = targetClass.getName();
186 if (targetClass.isHidden()) {
187 // use the original class name
188 name = name.replace('/', '_');
189 }
190 return name + "$$" + kind + "Specializer";
191 }
192
193 /**
194 * Generates a method handle for the {@code equals} method for a given data class
195 * @param receiverClass the data class
196 * @param getters the list of getters
197 * @return the method handle
198 */
199 private static MethodHandle makeEquals(MethodHandles.Lookup lookup, Class<?> receiverClass,
200 List<MethodHandle> getters) throws Throwable {
201 MethodType rr = MethodType.methodType(boolean.class, receiverClass, receiverClass);
202 MethodType ro = MethodType.methodType(boolean.class, receiverClass, Object.class);
203 MethodHandle instanceFalse = MethodHandles.dropArguments(FALSE, 0, receiverClass, Object.class); // (RO)Z
204 MethodHandle instanceTrue = MethodHandles.dropArguments(TRUE, 0, receiverClass, Object.class); // (RO)Z
205 MethodHandle isSameObject = OBJECT_EQ.asType(ro); // (RO)Z
206 MethodHandle isInstance = MethodHandles.dropArguments(CLASS_IS_INSTANCE.bindTo(receiverClass), 0, receiverClass); // (RO)Z
207 MethodHandle accumulator = MethodHandles.dropArguments(TRUE, 0, receiverClass, receiverClass); // (RR)Z
208
209 int size = getters.size();
210 MethodHandle[] equalators = new MethodHandle[size];
211 boolean hasPolymorphism = false;
212 for (int i = 0; i < size; i++) {
213 var getter = getters.get(i);
214 var type = getter.type().returnType();
215 if (isMonomorphic(type)) {
216 equalators[i] = equalator(lookup, type);
217 } else {
218 hasPolymorphism = true;
219 }
220 }
221
222 // Currently, hotspot does not support polymorphic inlining.
223 // As a result, if we have a MethodHandle to Object.equals,
224 // it does not enjoy separate profiles like individual invokevirtuals,
225 // and we must spin bytecode to accomplish separate profiling.
226 if (hasPolymorphism) {
227 String[] names = new String[size];
228
229 var classFileContext = ClassFile.of(ClassFile.ClassHierarchyResolverOption.of(ClassHierarchyResolver.ofClassLoading(lookup)));
230 var bytes = classFileContext.build(ClassDesc.of(specializerClassName(lookup.lookupClass(), "Equalator")), clb -> {
231 for (int i = 0; i < size; i++) {
232 if (equalators[i] == null) {
233 var name = "equalator".concat(Integer.toString(i));
234 names[i] = name;
235 var type = getters.get(i).type().returnType();
236 boolean isInterface = type.isInterface();
237 var typeDesc = type.describeConstable().orElseThrow();
238 clb.withMethodBody(name, MethodTypeDesc.of(CD_boolean, typeDesc, typeDesc), ACC_STATIC, cob -> {
239 var nonNullPath = cob.newLabel();
240 var fail = cob.newLabel();
241 cob.aload(0)
242 .ifnonnull(nonNullPath)
243 .aload(1)
244 .ifnonnull(fail)
245 .iconst_1() // arg0 null, arg1 null
246 .ireturn()
247 .labelBinding(fail)
248 .iconst_0() // arg0 null, arg1 non-null
249 .ireturn()
250 .labelBinding(nonNullPath)
251 .aload(0) // arg0.equals(arg1) - bytecode subject to customized profiling
252 .aload(1)
253 .invoke(isInterface ? Opcode.INVOKEINTERFACE : Opcode.INVOKEVIRTUAL, typeDesc, "equals", MTD_OBJECT_BOOLEAN, isInterface)
254 .ireturn();
255 });
256 }
257 }
258 });
259
260 var specializerLookup = lookup.defineHiddenClass(bytes, true, MethodHandles.Lookup.ClassOption.STRONG);
261
262 for (int i = 0; i < size; i++) {
263 if (equalators[i] == null) {
264 var type = getters.get(i).type().returnType();
265 equalators[i] = specializerLookup.findStatic(specializerLookup.lookupClass(), names[i], MethodType.methodType(boolean.class, type, type));
266 }
267 }
268 }
269
270 for (int i = 0; i < size; i++) {
271 var getter = getters.get(i);
272 MethodHandle equalator = equalators[i]; // (TT)Z
273 MethodHandle thisFieldEqual = MethodHandles.filterArguments(equalator, 0, getter, getter); // (RR)Z
274 accumulator = MethodHandles.guardWithTest(thisFieldEqual, accumulator, instanceFalse.asType(rr));
275 }
276
277 return MethodHandles.guardWithTest(isSameObject,
278 instanceTrue,
279 MethodHandles.guardWithTest(isInstance, accumulator.asType(ro), instanceFalse));
280 }
281
282 /**
283 * Generates a method handle for the {@code hashCode} method for a given data class
284 * @param receiverClass the data class
285 * @param getters the list of getters
286 * @return the method handle
287 */
288 private static MethodHandle makeHashCode(MethodHandles.Lookup lookup, Class<?> receiverClass,
289 List<MethodHandle> getters) throws Throwable {
290 MethodHandle accumulator = MethodHandles.dropArguments(ZERO, 0, receiverClass); // (R)I
291
292 int size = getters.size();
293 MethodHandle[] hashers = new MethodHandle[size];
294 boolean hasPolymorphism = false;
295 for (int i = 0; i < size; i++) {
296 var getter = getters.get(i);
297 var type = getter.type().returnType();
298 if (isMonomorphic(type)) {
299 hashers[i] = hasher(lookup, type);
300 } else {
301 hasPolymorphism = true;
302 }
303 }
304
305 // Currently, hotspot does not support polymorphic inlining.
306 // As a result, if we have a MethodHandle to Object.hashCode,
307 // it does not enjoy separate profiles like individual invokevirtuals,
308 // and we must spin bytecode to accomplish separate profiling.
309 if (hasPolymorphism) {
310 String[] names = new String[size];
311
312 var classFileContext = ClassFile.of(ClassFile.ClassHierarchyResolverOption.of(ClassHierarchyResolver.ofClassLoading(lookup)));
313 var bytes = classFileContext.build(ClassDesc.of(specializerClassName(lookup.lookupClass(), "Hasher")), clb -> {
314 for (int i = 0; i < size; i++) {
315 if (hashers[i] == null) {
316 var name = "hasher".concat(Integer.toString(i));
317 names[i] = name;
318 var type = getters.get(i).type().returnType();
319 boolean isInterface = type.isInterface();
320 var typeDesc = type.describeConstable().orElseThrow();
321 clb.withMethodBody(name, MethodTypeDesc.of(CD_int, typeDesc), ACC_STATIC, cob -> {
322 var nonNullPath = cob.newLabel();
323 cob.aload(0)
324 .ifnonnull(nonNullPath)
325 .iconst_0() // null hash is 0
326 .ireturn()
327 .labelBinding(nonNullPath)
328 .aload(0) // arg0.hashCode() - bytecode subject to customized profiling
329 .invoke(isInterface ? Opcode.INVOKEINTERFACE : Opcode.INVOKEVIRTUAL, typeDesc, "hashCode", MTD_INT, isInterface)
330 .ireturn();
331 });
332 }
333 }
334 });
335
336 var specializerLookup = lookup.defineHiddenClass(bytes, true, MethodHandles.Lookup.ClassOption.STRONG);
337
338 for (int i = 0; i < size; i++) {
339 if (hashers[i] == null) {
340 var type = getters.get(i).type().returnType();
341 hashers[i] = specializerLookup.findStatic(specializerLookup.lookupClass(), names[i], MethodType.methodType(int.class, type));
342 }
343 }
344 }
345
346 // @@@ Use loop combinator instead?
347 for (int i = 0; i < size; i++) {
348 var getter = getters.get(i);
349 MethodHandle hasher = hashers[i]; // (T)I
350 MethodHandle hashThisField = MethodHandles.filterArguments(hasher, 0, getter); // (R)I
351 MethodHandle combineHashes = MethodHandles.filterArguments(HASH_COMBINER, 0, accumulator, hashThisField); // (RR)I
352 accumulator = MethodHandles.permuteArguments(combineHashes, accumulator.type(), 0, 0); // adapt (R)I to (RR)I
353 }
354
355 return accumulator;
356 }
357
358 /**
359 * Generates a method handle for the {@code toString} method for a given data class
360 * @param receiverClass the data class
361 * @param getters the list of getters
362 * @param names the names
363 * @return the method handle
364 */
365 private static MethodHandle makeToString(MethodHandles.Lookup lookup,
366 Class<?> receiverClass,
367 MethodHandle[] getters,
368 List<String> names) {
369 assert getters.length == names.size();
370 if (getters.length == 0) {
371 // special case
372 MethodHandle emptyRecordCase = MethodHandles.constant(String.class, receiverClass.getSimpleName() + "[]");
373 emptyRecordCase = MethodHandles.dropArguments(emptyRecordCase, 0, receiverClass); // (R)S
374 return emptyRecordCase;
375 }
376
377 boolean firstTime = true;
378 MethodHandle[] mhs;
379 List<List<MethodHandle>> splits;
380 MethodHandle[] toSplit = getters;
381 int namesIndex = 0;
382 do {
383 /* StringConcatFactory::makeConcatWithConstants can only deal with 200 slots, longs and double occupy two
384 * the rest 1 slot, we need to chop the current `getters` into chunks, it could be that for records with
385 * a lot of components that we need to do a couple of iterations. The main difference between the first
386 * iteration and the rest would be on the recipe
387 */
388 splits = split(toSplit);
389 mhs = new MethodHandle[splits.size()];
390 for (int splitIndex = 0; splitIndex < splits.size(); splitIndex++) {
391 String recipe = "";
392 if (firstTime && splitIndex == 0) {
393 recipe = receiverClass.getSimpleName() + "[";
394 }
395 for (int i = 0; i < splits.get(splitIndex).size(); i++) {
396 recipe += firstTime ? names.get(namesIndex) + "=" + "\1" : "\1";
397 if (firstTime && namesIndex != names.size() - 1) {
398 recipe += ", ";
399 }
400 namesIndex++;
401 }
402 if (firstTime && splitIndex == splits.size() - 1) {
403 recipe += "]";
404 }
405 Class<?>[] concatTypeArgs = new Class<?>[splits.get(splitIndex).size()];
406 // special case: no need to create another getters if there is only one split
407 MethodHandle[] currentSplitGetters = new MethodHandle[splits.get(splitIndex).size()];
408 for (int j = 0; j < splits.get(splitIndex).size(); j++) {
409 concatTypeArgs[j] = splits.get(splitIndex).get(j).type().returnType();
410 currentSplitGetters[j] = splits.get(splitIndex).get(j);
411 }
412 MethodType concatMT = MethodType.methodType(String.class, concatTypeArgs);
413 try {
414 mhs[splitIndex] = StringConcatFactory.makeConcatWithConstants(
415 lookup, "",
416 concatMT,
417 recipe,
418 new Object[0]
419 ).getTarget();
420 mhs[splitIndex] = MethodHandles.filterArguments(mhs[splitIndex], 0, currentSplitGetters);
421 // this will spread the receiver class across all the getters
422 mhs[splitIndex] = MethodHandles.permuteArguments(
423 mhs[splitIndex],
424 MethodType.methodType(String.class, receiverClass),
425 new int[splits.get(splitIndex).size()]
426 );
427 } catch (Throwable t) {
428 throw new RuntimeException(t);
429 }
430 }
431 toSplit = mhs;
432 firstTime = false;
433 } while (splits.size() > 1);
434 return mhs[0];
435 }
436
437 /**
438 * Chops the getters into smaller chunks according to the maximum number of slots
439 * StringConcatFactory::makeConcatWithConstants can chew
440 * @param getters the current getters
441 * @return chunks that won't surpass the maximum number of slots StringConcatFactory::makeConcatWithConstants can chew
442 */
443 private static List<List<MethodHandle>> split(MethodHandle[] getters) {
444 List<List<MethodHandle>> splits = new ArrayList<>();
445
446 int slots = 0;
447
448 // Need to peel, so that neither call has more than acceptable number
449 // of slots for the arguments.
450 List<MethodHandle> cArgs = new ArrayList<>();
451 for (MethodHandle methodHandle : getters) {
452 Class<?> returnType = methodHandle.type().returnType();
453 int needSlots = (returnType == long.class || returnType == double.class) ? 2 : 1;
454 if (slots + needSlots > MAX_STRING_CONCAT_SLOTS) {
455 splits.add(cArgs);
456 cArgs = new ArrayList<>();
457 slots = 0;
458 }
459 cArgs.add(methodHandle);
460 slots += needSlots;
461 }
462
463 // Flush the tail slice
464 if (!cArgs.isEmpty()) {
465 splits.add(cArgs);
466 }
467
468 return splits;
469 }
470
471 /**
472 * Bootstrap method to generate the {@link Object#equals(Object)},
473 * {@link Object#hashCode()}, and {@link Object#toString()} methods, based
474 * on a description of the component names and accessor methods, for either
475 * {@code invokedynamic} call sites or dynamic constant pool entries.
476 *
477 * For more detail on the semantics of the generated methods see the specification
478 * of {@link java.lang.Record#equals(Object)}, {@link java.lang.Record#hashCode()} and
479 * {@link java.lang.Record#toString()}.
480 *
481 *
482 * @param lookup Every bootstrap method is expected to have a {@code lookup}
483 * which usually represents a lookup context with the
484 * accessibility privileges of the caller. This is because
485 * {@code invokedynamic} call sites always provide a {@code lookup}
486 * to the corresponding bootstrap method, but this method just
487 * ignores the {@code lookup} parameter
488 * @param methodName the name of the method to generate, which must be one of
489 * {@code "equals"}, {@code "hashCode"}, or {@code "toString"}
490 * @param type a {@link MethodType} corresponding the descriptor type
491 * for the method, which must correspond to the descriptor
492 * for the corresponding {@link Object} method, if linking
493 * an {@code invokedynamic} call site, or the
494 * constant {@code MethodHandle.class}, if linking a
495 * dynamic constant
496 * @param recordClass the record class hosting the record components
497 * @param names the list of component names, joined into a string
498 * separated by ";", or the empty string if there are no
499 * components. This parameter is ignored if the {@code methodName}
500 * parameter is {@code "equals"} or {@code "hashCode"}
501 * @param getters method handles for the accessor methods for the components
502 * @return a call site if invoked by indy, or a method handle
503 * if invoked by a condy
504 * @throws IllegalArgumentException if the bootstrap arguments are invalid
505 * or inconsistent
506 * @throws NullPointerException if any argument is {@code null} or if any element
507 * in the {@code getters} array is {@code null}
508 * @throws Throwable if any exception is thrown during call site construction
509 */
510 public static Object bootstrap(MethodHandles.Lookup lookup, String methodName, TypeDescriptor type,
511 Class<?> recordClass,
512 String names,
513 MethodHandle... getters) throws Throwable {
514 requireNonNull(lookup);
515 requireNonNull(methodName);
516 requireNonNull(type);
517 requireNonNull(recordClass);
518 requireNonNull(names);
519 requireNonNull(getters);
520 Arrays.stream(getters).forEach(Objects::requireNonNull);
521 MethodType methodType;
522 if (type instanceof MethodType mt)
523 methodType = mt;
524 else {
525 methodType = null;
526 if (!MethodHandle.class.equals(type))
527 throw new IllegalArgumentException(type.toString());
528 }
529 List<MethodHandle> getterList = List.of(getters);
530 MethodHandle handle = switch (methodName) {
531 case "equals" -> {
532 if (methodType != null && !methodType.equals(MethodType.methodType(boolean.class, recordClass, Object.class)))
533 throw new IllegalArgumentException("Bad method type: " + methodType);
534 yield makeEquals(lookup, recordClass, getterList);
535 }
536 case "hashCode" -> {
537 if (methodType != null && !methodType.equals(MethodType.methodType(int.class, recordClass)))
538 throw new IllegalArgumentException("Bad method type: " + methodType);
539 yield makeHashCode(lookup, recordClass, getterList);
540 }
541 case "toString" -> {
542 if (methodType != null && !methodType.equals(MethodType.methodType(String.class, recordClass)))
543 throw new IllegalArgumentException("Bad method type: " + methodType);
544 List<String> nameList = "".equals(names) ? List.of() : List.of(names.split(";"));
545 if (nameList.size() != getterList.size())
546 throw new IllegalArgumentException("Name list and accessor list do not match");
547 yield makeToString(lookup, recordClass, getters, nameList);
548 }
549 default -> throw new IllegalArgumentException(methodName);
550 };
551 return methodType != null ? new ConstantCallSite(handle) : handle;
552 }
553 }