90 System.out.println("generate BSM for " + lookup + "::" + name);
91 }
92 switch (name) {
93 case "hashCode":
94 return hashCodeInvoker(lookup, name, methodType);
95 case "equals":
96 return equalsInvoker(lookup, name, methodType);
97 case "toString":
98 return toStringInvoker(lookup, name, methodType);
99 default:
100 throw new IllegalArgumentException(name + " not valid method name");
101 }
102 }
103
104 static class MethodHandleBuilder {
105 static MethodHandle[] getters(Lookup lookup) {
106 return getters(lookup, null);
107 }
108
109 static MethodHandle[] getters(Lookup lookup, Comparator<MethodHandle> comparator) {
110 Class<?> type = lookup.lookupClass().asValueType();
111 // filter static fields and synthetic fields
112 Stream<MethodHandle> s = Arrays.stream(type.getDeclaredFields())
113 .filter(f -> !Modifier.isStatic(f.getModifiers()) && !f.isSynthetic())
114 .map(f -> {
115 try {
116 return lookup.unreflectGetter(f);
117 } catch (IllegalAccessException e) {
118 throw newLinkageError(e);
119 }
120 });
121 if (comparator != null) {
122 s = s.sorted(comparator);
123 }
124 return s.toArray(MethodHandle[]::new);
125 }
126
127 static MethodHandle primitiveEquals(Class<?> primitiveType) {
128 int index = Wrapper.forPrimitiveType(primitiveType).ordinal();
129 return EQUALS[index];
130 }
156
157 /*
158 * Produces a MethodHandle that returns boolean if two instances
159 * of the given interface class are substitutable.
160 *
161 * Two interface values are i== iff
162 * 1. if o1 and o2 are both reference objects then o1 r== o2; or
163 * 2. if o1 and o2 are both values then o1 v== o2
164 */
165 static MethodHandle interfaceEquals(Class<?> type) {
166 assert type.isInterface() || type == Object.class;
167 MethodType mt = methodType(boolean.class, type, type);
168 return guardWithTest(IS_SAME_VALUE_CLASS.asType(mt), VALUE_EQUALS.asType(mt), referenceEquals(type));
169 }
170
171 /*
172 * Produces a MethodHandle that returns boolean if two value instances
173 * of the given value class are substitutable.
174 */
175 static MethodHandle valueEquals(Class<?> c) {
176 assert c.isValue();
177 Class<?> type = c.asValueType();
178 MethodType mt = methodType(boolean.class, type, type);
179 MethodHandles.Lookup lookup = new MethodHandles.Lookup(type);
180 MethodHandle[] getters = getters(lookup, TYPE_SORTER);
181 MethodHandle instanceFalse = dropArguments(FALSE, 0, type, Object.class).asType(mt);
182 MethodHandle accumulator = dropArguments(TRUE, 0, type, type);
183 for (MethodHandle getter : getters) {
184 Class<?> ftype = getter.type().returnType();
185 MethodHandle eq = substitutableInvoker(ftype).asType(methodType(boolean.class, ftype, ftype));
186 MethodHandle thisFieldEqual = filterArguments(eq, 0, getter, getter);
187 accumulator = guardWithTest(thisFieldEqual, accumulator, instanceFalse);
188 }
189 // if both arguments are null, return true;
190 // otherwise return accumulator;
191 MethodHandle instanceTrue = dropArguments(TRUE, 0, type, Object.class).asType(mt);
192 return guardWithTest(IS_NULL.asType(mt),
193 instanceTrue.asType(mt),
194 guardWithTest(IS_SAME_VALUE_CLASS.asType(mt),
195 accumulator,
196 instanceFalse));
197 }
200 private static boolean eq(byte a, byte b) { return a == b; }
201 private static boolean eq(short a, short b) { return a == b; }
202 private static boolean eq(char a, char b) { return a == b; }
203 private static boolean eq(int a, int b) { return a == b; }
204 private static boolean eq(long a, long b) { return a == b; }
205 private static boolean eq(float a, float b) { return Float.compare(a, b) == 0; }
206 private static boolean eq(double a, double b) { return Double.compare(a, b) == 0; }
207 private static boolean eq(boolean a, boolean b) { return a == b; }
208 private static boolean eq(Object a, Object b) { return a == b; }
209
210 private static boolean isNull(Object a, Object b) {
211 // avoid acmp that will call isSubstitutable
212 if (a != null) return false;
213 if (b != null) return false;
214 return true;
215 }
216
217 private static boolean isSameValueClass(Object a, Object b) {
218 if (a == null || b == null)
219 return false;
220 return a.getClass().isValue() && a.getClass().asBoxType() == b.getClass().asBoxType();
221 }
222
223 private static boolean valueEq(Object a, Object b) {
224 assert isSameValueClass(a, b);
225 try {
226 Class<?> type = a.getClass();
227 return (boolean) valueEquals(type).invoke(type.cast(a), type.cast(b));
228 } catch (Error|RuntimeException e) {
229 throw e;
230 } catch (Throwable e) {
231 throw new InternalError(e);
232 }
233 }
234
235 private static String toString(Object o) {
236 return o != null ? o.toString() : "null";
237 }
238
239 private static MethodHandle toString(Class<?> type) {
240 if (type.isArray()) {
331 }
332 }
333
334 /**
335 * A "salt" value used for this internal hashcode implementation that
336 * needs to vary sufficiently from one run to the next so that
337 * the default hashcode for value types will vary between JVM runs.
338 */
339 static final int SALT;
340 static {
341 long nt = System.nanoTime();
342 int value = (int)((nt >>> 32) ^ nt);
343 SALT = GetIntegerAction.privilegedGetProperty("value.bsm.salt", value);
344 }
345 }
346
347 /*
348 * Produces a method handle that computes the hashcode
349 */
350 private static MethodHandle hashCodeInvoker(Lookup lookup, String name, MethodType mt) {
351 Class<?> type = lookup.lookupClass().asValueType();
352 MethodHandle target = dropArguments(constant(int.class, SALT), 0, type);
353 MethodHandle cls = dropArguments(constant(Class.class, type),0, type);
354 MethodHandle classHashCode = filterReturnValue(cls, hashCodeForType(Class.class));
355 MethodHandle combiner = filterArguments(HASH_COMBINER, 0, target, classHashCode);
356 // int v = SALT * 31 + type.hashCode();
357 MethodHandle init = permuteArguments(combiner, target.type(), 0, 0);
358 MethodHandle[] getters = MethodHandleBuilder.getters(lookup);
359 MethodHandle iterations = dropArguments(constant(int.class, getters.length), 0, type);
360 MethodHandle[] hashers = new MethodHandle[getters.length];
361 for (int i=0; i < getters.length; i++) {
362 MethodHandle getter = getters[i];
363 MethodHandle hasher = hashCodeForType(getter.type().returnType());
364 hashers[i] = filterReturnValue(getter, hasher);
365 }
366
367 // for (int i=0; i < getters.length; i++) {
368 // v = computeHash(v, i, a);
369 // }
370 MethodHandle body = COMPUTE_HASH.bindTo(hashers)
371 .asType(methodType(int.class, int.class, int.class, type));
372 return countedLoop(iterations, init, body);
373 }
374
375 /*
376 * Produces a method handle that invokes the toString method of a value object.
377 */
378 private static MethodHandle toStringInvoker(Lookup lookup, String name, MethodType mt) {
379 Class<?> type = lookup.lookupClass().asValueType();
380 MethodHandle[] getters = MethodHandleBuilder.getters(lookup);
381 int length = getters.length;
382 StringBuilder format = new StringBuilder();
383 Class<?>[] parameterTypes = new Class<?>[length];
384 // append the value class name
385 format.append("[").append(type.getName());
386 String separator = " ";
387 for (int i = 0; i < length; i++) {
388 MethodHandle getter = getters[i];
389 MethodHandleInfo fieldInfo = lookup.revealDirect(getter);
390 Class<?> ftype = fieldInfo.getMethodType().returnType();
391 format.append(separator)
392 .append(fieldInfo.getName())
393 .append("=\1");
394 getters[i]= filterReturnValue(getter, MethodHandleBuilder.toString(ftype));
395 parameterTypes[i] = String.class;
396 }
397 format.append("]");
398 try {
399 MethodHandle target = StringConcatFactory.makeConcatWithConstants(lookup, "toString",
400 methodType(String.class, parameterTypes), format.toString()).dynamicInvoker();
401 // apply getters
402 target = filterArguments(target, 0, getters);
403 // duplicate "this" argument from o::toString for each getter invocation
404 target = permuteArguments(target, methodType(String.class, type), new int[length]);
405 return target;
406 } catch (StringConcatException e) {
407 throw newLinkageError(e);
408 }
409 }
410
411 /*
412 * Produces a method handle that tests if two arguments are equals.
413 */
414 private static MethodHandle equalsInvoker(Lookup lookup, String name, MethodType mt) {
415 Class<?> type = lookup.lookupClass().asValueType();
416 // MethodHandle to compare all fields of two value objects
417 MethodHandle[] getters = MethodHandleBuilder.getters(lookup, TYPE_SORTER);
418 MethodHandle accumulator = dropArguments(TRUE, 0, type, type);
419 MethodHandle instanceFalse = dropArguments(FALSE, 0, type, Object.class)
420 .asType(methodType(boolean.class, type, type));
421 for (MethodHandle getter : getters) {
422 MethodHandle eq = equalsForType(getter.type().returnType());
423 MethodHandle thisFieldEqual = filterArguments(eq, 0, getter, getter);
424 accumulator = guardWithTest(thisFieldEqual, accumulator, instanceFalse);
425 }
426
427 // if o1 == o2 return true;
428 // if (o1 and o2 are same value class) return accumulator;
429 // return false;
430 MethodHandle instanceTrue = dropArguments(TRUE, 0, type, Object.class).asType(mt);
431 return guardWithTest(referenceEq().asType(mt),
432 instanceTrue.asType(mt),
433 guardWithTest(IS_SAME_VALUE_CLASS.asType(mt),
434 accumulator.asType(mt),
435 dropArguments(FALSE, 0, type, Object.class)));
503 * @apiNote
504 * This API is intended for performance evaluation of this idiom for
505 * {@code acmp}. Hence it is not in the {@link System} class.
506 *
507 * @param a an object
508 * @param b an object to be compared with {@code a} for substitutability
509 * @return {@code true} if the arguments are substitutable to each other;
510 * {@code false} otherwise.
511 * @param <T> type
512 * @see Float#equals(Object)
513 * @see Double#equals(Object)
514 */
515 public static <T> boolean isSubstitutable(T a, Object b) {
516 if (VERBOSE)
517 System.out.println("substitutable " + a + " vs " + b);
518 if (a == b) return true;
519 if (a == null || b == null) return false;
520 if (a.getClass() != b.getClass()) return false;
521
522 try {
523 Class<?> type = a.getClass().isValue() ? a.getClass().asValueType() : a.getClass();
524 return (boolean) substitutableInvoker(type).invoke(a, b);
525 } catch (Error|RuntimeException e) {
526 throw e;
527 } catch (Throwable e) {
528 if (VERBOSE) e.printStackTrace();
529 throw new InternalError(e);
530 }
531 }
532
533 /**
534 * Produces a method handle which tests if two arguments are
535 * {@linkplain #isSubstitutable(Object, Object) substitutable}.
536 *
537 * <ul>
538 * <li>If {@code T} is a non-floating point primitive type, this method
539 * returns a method handle testing the two arguments are the same value,
540 * i.e. {@code a == b}.
541 * <li>If {@code T} is {@code float} or {@code double}, this method
542 * returns a method handle representing {@link Float#equals(Object)} or
543 * {@link Double#equals(Object)} respectively.
549 * for all fields {@code f} declared in {@code T}, where {@code U} is
550 * the type of {@code f}, if {@code a.f} and {@code b.f} are substitutable
551 * with respect to {@code U}.
552 * <li>If {@code T} is an interface or {@code Object}, and
553 * {@code a} and {@code b} are of the same value class {@code V},
554 * this method returns a method handle that returns {@code true} if
555 * {@code a} and {@code b} are substitutable with respect to {@code V}.
556 * </ul>
557 *
558 * @param type class type
559 * @param <T> type
560 * @return a method handle for substitutability test
561 */
562 static <T> MethodHandle substitutableInvoker(Class<T> type) {
563 if (type.isPrimitive())
564 return MethodHandleBuilder.primitiveEquals(type);
565
566 if (type.isInterface() || type == Object.class)
567 return MethodHandleBuilder.interfaceEquals(type);
568
569 if (type.isValue())
570 return SUBST_TEST_METHOD_HANDLES.get(type);
571
572 return MethodHandleBuilder.referenceEquals(type);
573 }
574
575 // store the method handle for value types in ClassValue
576 private static ClassValue<MethodHandle> SUBST_TEST_METHOD_HANDLES = new ClassValue<>() {
577 @Override protected MethodHandle computeValue(Class<?> c) {
578 return MethodHandleBuilder.valueEquals(c);
579 }
580 };
581
582 private static final Comparator<MethodHandle> TYPE_SORTER = (mh1, mh2) -> {
583 // sort the getters with the return type
584 Class<?> t1 = mh1.type().returnType();
585 Class<?> t2 = mh2.type().returnType();
586 if (t1.isPrimitive()) {
587 if (!t2.isPrimitive()) {
588 return 1;
589 }
|
90 System.out.println("generate BSM for " + lookup + "::" + name);
91 }
92 switch (name) {
93 case "hashCode":
94 return hashCodeInvoker(lookup, name, methodType);
95 case "equals":
96 return equalsInvoker(lookup, name, methodType);
97 case "toString":
98 return toStringInvoker(lookup, name, methodType);
99 default:
100 throw new IllegalArgumentException(name + " not valid method name");
101 }
102 }
103
104 static class MethodHandleBuilder {
105 static MethodHandle[] getters(Lookup lookup) {
106 return getters(lookup, null);
107 }
108
109 static MethodHandle[] getters(Lookup lookup, Comparator<MethodHandle> comparator) {
110 Class<?> type = lookup.lookupClass().asPrimaryType();
111 // filter static fields and synthetic fields
112 Stream<MethodHandle> s = Arrays.stream(type.getDeclaredFields())
113 .filter(f -> !Modifier.isStatic(f.getModifiers()) && !f.isSynthetic())
114 .map(f -> {
115 try {
116 return lookup.unreflectGetter(f);
117 } catch (IllegalAccessException e) {
118 throw newLinkageError(e);
119 }
120 });
121 if (comparator != null) {
122 s = s.sorted(comparator);
123 }
124 return s.toArray(MethodHandle[]::new);
125 }
126
127 static MethodHandle primitiveEquals(Class<?> primitiveType) {
128 int index = Wrapper.forPrimitiveType(primitiveType).ordinal();
129 return EQUALS[index];
130 }
156
157 /*
158 * Produces a MethodHandle that returns boolean if two instances
159 * of the given interface class are substitutable.
160 *
161 * Two interface values are i== iff
162 * 1. if o1 and o2 are both reference objects then o1 r== o2; or
163 * 2. if o1 and o2 are both values then o1 v== o2
164 */
165 static MethodHandle interfaceEquals(Class<?> type) {
166 assert type.isInterface() || type == Object.class;
167 MethodType mt = methodType(boolean.class, type, type);
168 return guardWithTest(IS_SAME_VALUE_CLASS.asType(mt), VALUE_EQUALS.asType(mt), referenceEquals(type));
169 }
170
171 /*
172 * Produces a MethodHandle that returns boolean if two value instances
173 * of the given value class are substitutable.
174 */
175 static MethodHandle valueEquals(Class<?> c) {
176 assert c.isInlineClass();
177 Class<?> type = c.asPrimaryType();
178 MethodType mt = methodType(boolean.class, type, type);
179 MethodHandles.Lookup lookup = new MethodHandles.Lookup(type);
180 MethodHandle[] getters = getters(lookup, TYPE_SORTER);
181 MethodHandle instanceFalse = dropArguments(FALSE, 0, type, Object.class).asType(mt);
182 MethodHandle accumulator = dropArguments(TRUE, 0, type, type);
183 for (MethodHandle getter : getters) {
184 Class<?> ftype = getter.type().returnType();
185 MethodHandle eq = substitutableInvoker(ftype).asType(methodType(boolean.class, ftype, ftype));
186 MethodHandle thisFieldEqual = filterArguments(eq, 0, getter, getter);
187 accumulator = guardWithTest(thisFieldEqual, accumulator, instanceFalse);
188 }
189 // if both arguments are null, return true;
190 // otherwise return accumulator;
191 MethodHandle instanceTrue = dropArguments(TRUE, 0, type, Object.class).asType(mt);
192 return guardWithTest(IS_NULL.asType(mt),
193 instanceTrue.asType(mt),
194 guardWithTest(IS_SAME_VALUE_CLASS.asType(mt),
195 accumulator,
196 instanceFalse));
197 }
200 private static boolean eq(byte a, byte b) { return a == b; }
201 private static boolean eq(short a, short b) { return a == b; }
202 private static boolean eq(char a, char b) { return a == b; }
203 private static boolean eq(int a, int b) { return a == b; }
204 private static boolean eq(long a, long b) { return a == b; }
205 private static boolean eq(float a, float b) { return Float.compare(a, b) == 0; }
206 private static boolean eq(double a, double b) { return Double.compare(a, b) == 0; }
207 private static boolean eq(boolean a, boolean b) { return a == b; }
208 private static boolean eq(Object a, Object b) { return a == b; }
209
210 private static boolean isNull(Object a, Object b) {
211 // avoid acmp that will call isSubstitutable
212 if (a != null) return false;
213 if (b != null) return false;
214 return true;
215 }
216
217 private static boolean isSameValueClass(Object a, Object b) {
218 if (a == null || b == null)
219 return false;
220 return a.getClass().isInlineClass() && a.getClass().asNullableType() == b.getClass().asNullableType();
221 }
222
223 private static boolean valueEq(Object a, Object b) {
224 assert isSameValueClass(a, b);
225 try {
226 Class<?> type = a.getClass();
227 return (boolean) valueEquals(type).invoke(type.cast(a), type.cast(b));
228 } catch (Error|RuntimeException e) {
229 throw e;
230 } catch (Throwable e) {
231 throw new InternalError(e);
232 }
233 }
234
235 private static String toString(Object o) {
236 return o != null ? o.toString() : "null";
237 }
238
239 private static MethodHandle toString(Class<?> type) {
240 if (type.isArray()) {
331 }
332 }
333
334 /**
335 * A "salt" value used for this internal hashcode implementation that
336 * needs to vary sufficiently from one run to the next so that
337 * the default hashcode for value types will vary between JVM runs.
338 */
339 static final int SALT;
340 static {
341 long nt = System.nanoTime();
342 int value = (int)((nt >>> 32) ^ nt);
343 SALT = GetIntegerAction.privilegedGetProperty("value.bsm.salt", value);
344 }
345 }
346
347 /*
348 * Produces a method handle that computes the hashcode
349 */
350 private static MethodHandle hashCodeInvoker(Lookup lookup, String name, MethodType mt) {
351 Class<?> type = lookup.lookupClass().asPrimaryType();
352 MethodHandle target = dropArguments(constant(int.class, SALT), 0, type);
353 MethodHandle cls = dropArguments(constant(Class.class, type),0, type);
354 MethodHandle classHashCode = filterReturnValue(cls, hashCodeForType(Class.class));
355 MethodHandle combiner = filterArguments(HASH_COMBINER, 0, target, classHashCode);
356 // int v = SALT * 31 + type.hashCode();
357 MethodHandle init = permuteArguments(combiner, target.type(), 0, 0);
358 MethodHandle[] getters = MethodHandleBuilder.getters(lookup);
359 MethodHandle iterations = dropArguments(constant(int.class, getters.length), 0, type);
360 MethodHandle[] hashers = new MethodHandle[getters.length];
361 for (int i=0; i < getters.length; i++) {
362 MethodHandle getter = getters[i];
363 MethodHandle hasher = hashCodeForType(getter.type().returnType());
364 hashers[i] = filterReturnValue(getter, hasher);
365 }
366
367 // for (int i=0; i < getters.length; i++) {
368 // v = computeHash(v, i, a);
369 // }
370 MethodHandle body = COMPUTE_HASH.bindTo(hashers)
371 .asType(methodType(int.class, int.class, int.class, type));
372 return countedLoop(iterations, init, body);
373 }
374
375 /*
376 * Produces a method handle that invokes the toString method of a value object.
377 */
378 private static MethodHandle toStringInvoker(Lookup lookup, String name, MethodType mt) {
379 Class<?> type = lookup.lookupClass().asPrimaryType();
380 MethodHandle[] getters = MethodHandleBuilder.getters(lookup);
381 if (VERBOSE) {
382 System.out.println("getter: " + Arrays.toString(getters));
383 }
384 int length = getters.length;
385 StringBuilder format = new StringBuilder();
386 Class<?>[] parameterTypes = new Class<?>[length];
387 // append the value class name
388 format.append("[").append(type.getName());
389 String separator = " ";
390 for (int i = 0; i < length; i++) {
391 MethodHandle getter = getters[i];
392 MethodHandleInfo fieldInfo = lookup.revealDirect(getter);
393 if (VERBOSE) {
394 System.out.println("getter: " + getter + " field info: " + fieldInfo);
395 }
396 Class<?> ftype = fieldInfo.getMethodType().returnType();
397 format.append(separator)
398 .append(fieldInfo.getName())
399 .append("=\1");
400 getters[i]= filterReturnValue(getter, MethodHandleBuilder.toString(ftype));
401 parameterTypes[i] = String.class;
402 }
403 format.append("]");
404 try {
405 MethodHandle target = StringConcatFactory.makeConcatWithConstants(lookup, "toString",
406 methodType(String.class, parameterTypes), format.toString()).dynamicInvoker();
407 // apply getters
408 target = filterArguments(target, 0, getters);
409 // duplicate "this" argument from o::toString for each getter invocation
410 target = permuteArguments(target, methodType(String.class, type), new int[length]);
411 return target;
412 } catch (StringConcatException e) {
413 throw newLinkageError(e);
414 }
415 }
416
417 /*
418 * Produces a method handle that tests if two arguments are equals.
419 */
420 private static MethodHandle equalsInvoker(Lookup lookup, String name, MethodType mt) {
421 Class<?> type = lookup.lookupClass().asPrimaryType();
422 // MethodHandle to compare all fields of two value objects
423 MethodHandle[] getters = MethodHandleBuilder.getters(lookup, TYPE_SORTER);
424 MethodHandle accumulator = dropArguments(TRUE, 0, type, type);
425 MethodHandle instanceFalse = dropArguments(FALSE, 0, type, Object.class)
426 .asType(methodType(boolean.class, type, type));
427 for (MethodHandle getter : getters) {
428 MethodHandle eq = equalsForType(getter.type().returnType());
429 MethodHandle thisFieldEqual = filterArguments(eq, 0, getter, getter);
430 accumulator = guardWithTest(thisFieldEqual, accumulator, instanceFalse);
431 }
432
433 // if o1 == o2 return true;
434 // if (o1 and o2 are same value class) return accumulator;
435 // return false;
436 MethodHandle instanceTrue = dropArguments(TRUE, 0, type, Object.class).asType(mt);
437 return guardWithTest(referenceEq().asType(mt),
438 instanceTrue.asType(mt),
439 guardWithTest(IS_SAME_VALUE_CLASS.asType(mt),
440 accumulator.asType(mt),
441 dropArguments(FALSE, 0, type, Object.class)));
509 * @apiNote
510 * This API is intended for performance evaluation of this idiom for
511 * {@code acmp}. Hence it is not in the {@link System} class.
512 *
513 * @param a an object
514 * @param b an object to be compared with {@code a} for substitutability
515 * @return {@code true} if the arguments are substitutable to each other;
516 * {@code false} otherwise.
517 * @param <T> type
518 * @see Float#equals(Object)
519 * @see Double#equals(Object)
520 */
521 public static <T> boolean isSubstitutable(T a, Object b) {
522 if (VERBOSE)
523 System.out.println("substitutable " + a + " vs " + b);
524 if (a == b) return true;
525 if (a == null || b == null) return false;
526 if (a.getClass() != b.getClass()) return false;
527
528 try {
529 Class<?> type = a.getClass().isInlineClass() ? a.getClass().asPrimaryType() : a.getClass();
530 return (boolean) substitutableInvoker(type).invoke(a, b);
531 } catch (Error|RuntimeException e) {
532 throw e;
533 } catch (Throwable e) {
534 if (VERBOSE) e.printStackTrace();
535 throw new InternalError(e);
536 }
537 }
538
539 /**
540 * Produces a method handle which tests if two arguments are
541 * {@linkplain #isSubstitutable(Object, Object) substitutable}.
542 *
543 * <ul>
544 * <li>If {@code T} is a non-floating point primitive type, this method
545 * returns a method handle testing the two arguments are the same value,
546 * i.e. {@code a == b}.
547 * <li>If {@code T} is {@code float} or {@code double}, this method
548 * returns a method handle representing {@link Float#equals(Object)} or
549 * {@link Double#equals(Object)} respectively.
555 * for all fields {@code f} declared in {@code T}, where {@code U} is
556 * the type of {@code f}, if {@code a.f} and {@code b.f} are substitutable
557 * with respect to {@code U}.
558 * <li>If {@code T} is an interface or {@code Object}, and
559 * {@code a} and {@code b} are of the same value class {@code V},
560 * this method returns a method handle that returns {@code true} if
561 * {@code a} and {@code b} are substitutable with respect to {@code V}.
562 * </ul>
563 *
564 * @param type class type
565 * @param <T> type
566 * @return a method handle for substitutability test
567 */
568 static <T> MethodHandle substitutableInvoker(Class<T> type) {
569 if (type.isPrimitive())
570 return MethodHandleBuilder.primitiveEquals(type);
571
572 if (type.isInterface() || type == Object.class)
573 return MethodHandleBuilder.interfaceEquals(type);
574
575 if (type.isInlineClass())
576 return SUBST_TEST_METHOD_HANDLES.get(type);
577
578 return MethodHandleBuilder.referenceEquals(type);
579 }
580
581 // store the method handle for value types in ClassValue
582 private static ClassValue<MethodHandle> SUBST_TEST_METHOD_HANDLES = new ClassValue<>() {
583 @Override protected MethodHandle computeValue(Class<?> c) {
584 return MethodHandleBuilder.valueEquals(c);
585 }
586 };
587
588 private static final Comparator<MethodHandle> TYPE_SORTER = (mh1, mh2) -> {
589 // sort the getters with the return type
590 Class<?> t1 = mh1.type().returnType();
591 Class<?> t2 = mh2.type().returnType();
592 if (t1.isPrimitive()) {
593 if (!t2.isPrimitive()) {
594 return 1;
595 }
|