1 /* 2 * Copyright (c) 2018, 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 sun.invoke.util.Wrapper; 29 import sun.security.action.GetIntegerAction; 30 import sun.security.action.GetPropertyAction; 31 32 import java.lang.reflect.Modifier; 33 import java.util.Arrays; 34 import java.util.Comparator; 35 import java.util.Objects; 36 import java.util.stream.Stream; 37 38 import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP; 39 import static java.lang.invoke.MethodHandles.*; 40 import static java.lang.invoke.MethodType.*; 41 import static java.lang.invoke.ValueBootstrapMethods.MethodHandleBuilder.*; 42 43 44 /** 45 * Bootstrap methods for value types 46 */ 47 public final class ValueBootstrapMethods { 48 private ValueBootstrapMethods() {} 49 private static final boolean VERBOSE = 50 GetPropertyAction.privilegedGetProperty("value.bsm.debug") != null; 51 52 /** 53 * Makes a bootstrap method for the named operation for the given Class. 54 * 55 * @apiNote {@code c} parameter for testing purpose. This method will be removed. 56 * 57 * @param caller A lookup context 58 * @param name The name of the method to implement. 59 * @param type The expected signature of the {@code CallSite} 60 * @param c Class 61 * @return a CallSite whose target can be used to perform the named operation 62 */ 63 public static CallSite makeBootstrapMethod(MethodHandles.Lookup caller, 64 String name, 65 MethodType type, 66 Class<?> c) { 67 MethodHandles.Lookup lookup = caller; 68 if (caller.lookupClass() != c) { 69 lookup = new MethodHandles.Lookup(c); 70 } 71 return makeBootstrapMethod(lookup, name, type); 72 } 73 74 /** 75 * Makes a bootstrap method for the named operation for the given Class. 76 * 77 * @param lookup A lookup context 78 * @param name The name of the method to implement. 79 * @param type The expected signature of the {@code CallSite} 80 * @return a CallSite whose target can be used to perform the named operation 81 */ 82 public static CallSite makeBootstrapMethod(MethodHandles.Lookup lookup, 83 String name, 84 MethodType type) { 85 return new ConstantCallSite(generateTarget(lookup, name, type)); 86 } 87 88 private static MethodHandle generateTarget(Lookup lookup, String name, MethodType methodType) { 89 if (VERBOSE) { 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 } 131 132 static MethodHandle referenceEquals(Class<?> type) { 133 return EQUALS[Wrapper.OBJECT.ordinal()].asType(methodType(boolean.class, type, type)); 134 } 135 136 static MethodHandle referenceEq() { 137 return EQUALS[Wrapper.OBJECT.ordinal()]; 138 } 139 140 static MethodHandle hashCodeForType(Class<?> type) { 141 if (type.isPrimitive()) { 142 int index = Wrapper.forPrimitiveType(type).ordinal(); 143 return HASHCODE[index]; 144 } else { 145 return HASHCODE[Wrapper.OBJECT.ordinal()].asType(methodType(int.class, type)); 146 } 147 } 148 149 static MethodHandle equalsForType(Class<?> type) { 150 if (type.isPrimitive()) { 151 return primitiveEquals(type); 152 } else { 153 return OBJECTS_EQUALS.asType(methodType(boolean.class, type, type)); 154 } 155 } 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 } 198 199 // ------ utility methods ------ 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()) { 241 Class<?> componentType = type.getComponentType(); 242 if (componentType.isPrimitive()) { 243 int index = Wrapper.forPrimitiveType(componentType).ordinal(); 244 return ARRAYS_TO_STRING[index]; 245 } else { 246 return ARRAYS_TO_STRING[Wrapper.OBJECT.ordinal()].asType(methodType(String.class, type)); 247 } 248 } else { 249 return TO_STRING.asType(methodType(String.class, type)); 250 } 251 } 252 253 private static int hashCombiner(int accumulator, int value) { 254 return accumulator * 31 + value; 255 } 256 257 private static int computeHashCode(MethodHandle[] hashers, int v, int i, Object o) { 258 try { 259 int hc = (int)hashers[i].invoke(o); 260 return hashCombiner(v, hc); 261 } catch (Error|RuntimeException e) { 262 throw e; 263 } catch (Throwable e) { 264 throw new InternalError(e); 265 } 266 } 267 268 private static final MethodHandle[] EQUALS = initEquals(); 269 private static final MethodHandle[] ARRAYS_TO_STRING = initArraysToString(); 270 private static final MethodHandle[] HASHCODE = initHashCode(); 271 272 static final MethodHandle IS_SAME_VALUE_CLASS = 273 findStatic("isSameValueClass", methodType(boolean.class, Object.class, Object.class)); 274 static final MethodHandle VALUE_EQUALS = 275 findStatic("valueEq", methodType(boolean.class, Object.class, Object.class)); 276 static final MethodHandle IS_NULL = 277 findStatic("isNull", methodType(boolean.class, Object.class, Object.class)); 278 static final MethodHandle TO_STRING = 279 findStatic("toString", methodType(String.class, Object.class)); 280 static final MethodHandle OBJECTS_EQUALS = 281 findStatic(Objects.class, "equals", methodType(boolean.class, Object.class, Object.class)); 282 283 static final MethodHandle FALSE = constant(boolean.class, false); 284 static final MethodHandle TRUE = constant(boolean.class, true); 285 static final MethodHandle HASH_COMBINER = 286 findStatic("hashCombiner", methodType(int.class, int.class, int.class)); 287 static final MethodHandle COMPUTE_HASH = 288 findStatic("computeHashCode", methodType(int.class, MethodHandle[].class, int.class, int.class, Object.class)); 289 290 private static MethodHandle[] initEquals() { 291 MethodHandle[] mhs = new MethodHandle[Wrapper.COUNT]; 292 for (Wrapper wrapper : Wrapper.values()) { 293 if (wrapper == Wrapper.VOID) continue; 294 295 Class<?> type = wrapper.primitiveType(); 296 mhs[wrapper.ordinal()] = findStatic("eq", methodType(boolean.class, type, type)); 297 } 298 return mhs; 299 } 300 301 private static MethodHandle[] initArraysToString() { 302 MethodHandle[] mhs = new MethodHandle[Wrapper.COUNT]; 303 for (Wrapper wrapper : Wrapper.values()) { 304 if (wrapper == Wrapper.VOID) continue; 305 306 Class<?> arrayType = wrapper.arrayType(); 307 mhs[wrapper.ordinal()] = findStatic(Arrays.class, "toString", methodType(String.class, arrayType)); 308 } 309 return mhs; 310 } 311 312 private static MethodHandle[] initHashCode() { 313 MethodHandle[] mhs = new MethodHandle[Wrapper.COUNT]; 314 for (Wrapper wrapper : Wrapper.values()) { 315 if (wrapper == Wrapper.VOID) continue; 316 Class<?> cls = wrapper == Wrapper.OBJECT ? Objects.class : wrapper.wrapperType(); 317 mhs[wrapper.ordinal()] = findStatic(cls, "hashCode", 318 methodType(int.class, wrapper.primitiveType())); 319 } 320 return mhs; 321 } 322 323 private static MethodHandle findStatic(String name, MethodType methodType) { 324 return findStatic(MethodHandleBuilder.class, name, methodType); 325 } 326 private static MethodHandle findStatic(Class<?> cls, String name, MethodType methodType) { 327 try { 328 return IMPL_LOOKUP.findStatic(cls, name, methodType); 329 } catch (NoSuchMethodException|IllegalAccessException e) { 330 throw newLinkageError(e); 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))); 436 } 437 438 private static LinkageError newLinkageError(Throwable e) { 439 return (LinkageError) new LinkageError().initCause(e); 440 } 441 442 /** 443 * Returns {@code true} if the arguments are <em>substitutable</em> to each 444 * other and {@code false} otherwise. 445 * <em>Substitutability</em> means that they cannot be distinguished from 446 * each other in any data-dependent way, meaning that it is safe to substitute 447 * one for the other. 448 * 449 * <ul> 450 * <li>If {@code a} and {@code b} are both {@code null}, this method returns 451 * {@code true}. 452 * <li>If {@code a} and {@code b} are both value instances of the same class 453 * {@code V}, this method returns {@code true} if, for all fields {@code f} 454 * declared in {@code V}, {@code a.f} and {@code b.f} are substitutable. 455 * <li>If {@code a} and {@code b} are both primitives of the same type, 456 * this method returns {@code a == b} with the following exception: 457 * <ul> 458 * <li> If {@code a} and {@code b} both represent {@code NaN}, 459 * this method returns {@code true}, even though {@code NaN == NaN} 460 * has the value {@code false}. 461 * <li> If {@code a} is floating point positive zero while {@code b} is 462 * floating point negative zero, or vice versa, this method 463 * returns {@code false}, even though {@code +0.0 == -0.0} has 464 * the value {@code true}. 465 * </ul> 466 * <li>If {@code a} and {@code b} are both instances of the same reference type, 467 * this method returns {@code a == b}. 468 * <li>Otherwise this method returns {@code false}. 469 * </ul> 470 * 471 * <p>For example, 472 * <pre>{@code interface Number { ... } 473 * // ordinary reference class 474 * class IntNumber implements Number { ... } 475 * // value class 476 * value class IntValue implements Number { 477 * int i; 478 * : 479 * public static IntValue of(int i) {...} // IntValue::of creates a new value instance 480 * } 481 * // value class with an Object field 482 * value class RefValue { 483 * Object o; 484 * : 485 * } 486 * 487 * var val1 = IntValue.of(10); 488 * var val2 = IntValue.of(10); // val1 and val2 have the same value 489 * var ref1 = new IntNumber(10); // ref1 and ref2 are two reference instances 490 * var ref2 = new IntNumber(10); 491 * assertTrue(isSubstitutable(val1, val2)); // val1 and val2 are both value instances of IntValue 492 * assertFalse(isSubstitutable(ref1, ref2)); // ref1 and ref2 are two reference instances that are not substitutable 493 * assertTrue(isSubstitutable(ref1, ref1)); // a reference instance is substitutable with itself 494 * 495 * var rval1 = RefValue.of(List.of("list")); // rval1.o and rval2.o both contain a list of one-single element "list" 496 * var rval2 = RefValue.of(List.of("list"); 497 * var rval3 = RefValue.of(rval1.o); 498 * 499 * assertFalse(isSubstitutable(rval1, rval2)); // rval1.o and rval2.o are two different List instances and hence not substitutable 500 * assertTrue(isSubstitutable(rval1, rval3 )); // rval1.o and rval3.o are the same reference instance 501 * }</pre> 502 * 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. 544 * <li>If {@code T} is a reference type that is not {@code Object} and not an 545 * interface, this method returns a method handle testing 546 * the two arguments are the same reference, i.e. {@code a == b}. 547 * <li>If {@code T} is a value type, this method returns 548 * a method handle that returns {@code true} if 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 } 590 } else { 591 if (t2.isPrimitive()) { 592 return -1; 593 } 594 } 595 return -1; 596 }; 597 }