1 /*
   2  * Copyright (c) 2016, 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 jdk.experimental.value;
  27 
  28 import java.lang.invoke.MethodHandle;
  29 import java.lang.invoke.MethodHandles;
  30 import java.lang.invoke.MethodHandles.Lookup;
  31 import java.lang.invoke.MethodType;
  32 import java.lang.reflect.Field;
  33 import java.lang.reflect.Modifier;
  34 import java.util.Arrays;
  35 import java.util.List;
  36 import java.util.Map;
  37 import java.util.Objects;
  38 import java.util.concurrent.ConcurrentHashMap;
  39 import java.util.function.Consumer;
  40 import java.util.function.Supplier;
  41 import java.util.stream.Collectors;
  42 import java.util.stream.IntStream;
  43 import java.util.stream.Stream;
  44 
  45 import jdk.experimental.bytecode.AnnotationsBuilder.Kind;
  46 import jdk.experimental.bytecode.Flag;
  47 import jdk.experimental.bytecode.Opcode;
  48 import jdk.experimental.value.MethodHandleBuilder.IsolatedMethodBuilder;
  49 import jdk.experimental.value.MethodHandleBuilder.MethodHandleCodeBuilder;
  50 import jdk.experimental.value.ValueType.ValueHandleKind.ValueHandleKey;
  51 import jdk.experimental.bytecode.MacroCodeBuilder.CondKind;
  52 import jdk.experimental.bytecode.TypeTag;
  53 import jdk.internal.misc.Unsafe;
  54 import sun.invoke.util.BytecodeDescriptor;
  55 import sun.invoke.util.Wrapper;
  56 import valhalla.shady.MinimalValueTypes_1_0;
  57 
  58 // Rough place holder just now...
  59 public class ValueType<T> {
  60 
  61     enum ValueHandleKind {
  62         BOX("box"),
  63         UNBOX("unbox"),
  64         DEFAULT("defaultValueConstant"),
  65         EQ("substitutabilityTest"),
  66         HASH("substitutabilityHashCode"),
  67         ARRAYLENGTH("arrayLength"),
  68         WITHER("findWither", Lookup.class, String.class, Class.class) {
  69             @Override
  70             ValueHandleKey key(Object fieldName) {
  71                return new ValueHandleKey(this, fieldName);
  72             }
  73         },
  74         UNREFLECT_WITHERS("unreflectWithers", Lookup.class, boolean.class, Field[].class) {
  75             @Override
  76             ValueHandleKey key(Object fields) {
  77                 return new ValueHandleKey(this, fields);
  78             }
  79         },
  80         NEWARRAY("newArray"),
  81         VALOAD("arrayGetter"),
  82         VASTORE("arraySetter"),
  83         MULTINEWARRAY("newMultiArray", int.class) {
  84             @Override
  85             ValueHandleKey key(Object dims) {
  86                return new ValueHandleKey(this, dims);
  87             }
  88         },
  89         IDENTITY("identity"),
  90         GETTER("findGetter", Lookup.class, String.class, Class.class) {
  91             @Override
  92             ValueHandleKey key(Object fieldName) {
  93                return new ValueHandleKey(this, fieldName);
  94             }
  95         };
  96 
  97         final MethodHandle handle;
  98 
  99         ValueHandleKind(String handleName, Class<?>... argtypes) {
 100             try {
 101                 this.handle = MethodHandles.lookup().findVirtual(ValueType.class, handleName, MethodType.methodType(MethodHandle.class, argtypes));
 102             } catch (ReflectiveOperationException ex) {
 103                 throw new IllegalArgumentException("Cannot initialize value handle key for: " + handleName);
 104             }
 105         }
 106 
 107         String handleName() {
 108             return MethodHandles.lookup().revealDirect(handle).getName();
 109         }
 110 
 111         MethodType handleType() {
 112             return MethodHandles.lookup().revealDirect(handle).getMethodType();
 113         }
 114 
 115         ValueHandleKey key() {
 116             return new ValueHandleKey(this, null);
 117         }
 118 
 119         ValueHandleKey key(Object optArg) {
 120             throw new IllegalStateException();
 121         }
 122 
 123         static class ValueHandleKey {
 124             ValueHandleKind kind;
 125             Object optArg;
 126 
 127             ValueHandleKey(ValueHandleKind kind, Object optArg) {
 128                 this.kind = kind;
 129                 this.optArg = optArg;
 130             }
 131 
 132             @Override
 133             public boolean equals(Object obj) {
 134                 if (obj instanceof ValueHandleKey) {
 135                     ValueHandleKey that = (ValueHandleKey)obj;
 136                     return Objects.equals(kind, that.kind) &&
 137                             Objects.equals(optArg, that.optArg);
 138                 } else {
 139                     return false;
 140                 }
 141             }
 142 
 143             @Override
 144             public int hashCode() {
 145                 return Objects.hashCode(kind) * 31 + Objects.hashCode(optArg);
 146             }
 147         }
 148     }
 149 
 150     private static final Lookup IMPL_LOOKUP;
 151 
 152     static {
 153         try {
 154             Field f = Lookup.class.getDeclaredField("IMPL_LOOKUP");
 155             f.setAccessible(true);
 156             IMPL_LOOKUP = (Lookup)f.get(null);
 157         } catch (ReflectiveOperationException ex) {
 158             throw new AssertionError(ex);
 159         }
 160     }
 161 
 162     private static final ConcurrentHashMap<Class<?>, ValueType<?>> BOX_TO_VT = new ConcurrentHashMap<>();
 163 
 164     public static boolean classHasValueType(Class<?> x) {
 165         if (!MinimalValueTypes_1_0.isValueCapable(x)) {
 166             return false;
 167         }
 168         return MinimalValueTypes_1_0.getValueTypeClass(x) != null;
 169     }
 170 
 171     @SuppressWarnings("unchecked")
 172     public static <T> ValueType<T> forClass(Class<T> x) {
 173         if (!MinimalValueTypes_1_0.isValueCapable(x)) {
 174             throw new IllegalArgumentException("Class " + x + " not a value capable class");
 175         }
 176 
 177         ValueType<T> vt = (ValueType<T>) BOX_TO_VT.get(x);
 178         if (vt != null) {
 179             return vt;
 180         }
 181 
 182         Class<T> valueClass = (Class<T>) MinimalValueTypes_1_0.getValueTypeClass(x);
 183         vt = new ValueType<T>(x, valueClass);
 184         ValueType<T> old = (ValueType<T>) BOX_TO_VT.putIfAbsent(x, vt);
 185         if (old != null) {
 186             vt = old;
 187         }
 188         return vt;
 189     }
 190 
 191     public static <T> ValueType<T> make(Lookup lookup, String name, String[] fieldNames, Class<?>... fieldTypes) throws ReflectiveOperationException {
 192         if (fieldNames.length != fieldTypes.length) {
 193             throw new IllegalArgumentException("Field names length and field types length must match");
 194         }
 195         if (!(fieldNames.length > 0)) {
 196             throw new IllegalArgumentException("Field length must be greater than zero");
 197         }
 198         IsolatedMethodBuilder builder = new IsolatedMethodBuilder(name, lookup);
 199         builder.withMajorVersion(53)
 200                .withMinorVersion(0)
 201                .withSuperclass(Object.class)
 202                .withFlags(Flag.ACC_FINAL)
 203                .withAnnotation(Kind.RUNTIME_VISIBLE, "Ljvm/internal/value/ValueCapableClass;");
 204         //add fields
 205         for (int i = 0 ; i < fieldNames.length ; i++) {
 206             builder.withField(fieldNames[i], BytecodeDescriptor.unparse(fieldTypes[i]), F -> F.withFlags(Flag.ACC_FINAL));
 207         }
 208         //add constructor
 209         String ctype = BytecodeDescriptor.unparseMethod(void.class, fieldTypes);
 210         builder.withMethod("<init>", ctype, M -> M.withCode(MethodHandleCodeBuilder::new, C -> {
 211                 C.aload_0().invokespecial(Object.class, "<init>", "()V", false);
 212                 int l = 1;
 213                 for (int i = 0 ; i < fieldNames.length ; i++) {
 214                     String fType = BytecodeDescriptor.unparse(fieldTypes[i]);
 215                     C.aload_0().load(l).putfield(builder.thisClass(), fieldNames[i], fType);
 216                     l += Wrapper.forBasicType(fieldTypes[i]).stackSlots();
 217                 }
 218                 C.return_();
 219         }));
 220         //add equals and hashCode
 221         builder.withMethod("equals", "(Ljava/lang/Object;)Z", M ->
 222             M.withFlags(Flag.ACC_PUBLIC).withCode(MethodHandleCodeBuilder::new,
 223                     C -> substitutabilityTestBuilder(true, builder.thisClass(), FieldInfo.stream(fieldNames, fieldTypes), C)));
 224         builder.withMethod("hashCode", "()I", M ->
 225             M.withFlags(Flag.ACC_PUBLIC).withCode(MethodHandleCodeBuilder::new,
 226                     C -> substitutabilityHashCodeBuilder(builder.thisClass(), FieldInfo.stream(fieldNames, fieldTypes), C)));
 227         byte[] barr = builder.build();
 228         MinimalValueTypes_1_0.maybeDump(name, barr);
 229         @SuppressWarnings("unchecked")
 230         Class<T> vtClass = (Class<T>)lookup.defineClass(barr);
 231         return forClass(vtClass);
 232     }
 233 
 234     private Lookup boxLookup;
 235     private Lookup valueLookup;
 236     private Map<ValueHandleKind.ValueHandleKey, MethodHandle> handleMap = new ConcurrentHashMap<>();
 237 
 238     private ValueType(Class<T> boxClass, Class<T> valueClass) {
 239         this.boxLookup = IMPL_LOOKUP.in(boxClass);
 240         this.valueLookup = IMPL_LOOKUP.in(valueClass);
 241     }
 242 
 243     @SuppressWarnings("unchecked")
 244     public Class<T> boxClass() {
 245         return (Class<T>)boxLookup.lookupClass();
 246     }
 247 
 248     public Class<?> sourceClass() {
 249         return boxClass();
 250     }
 251 
 252     public Class<?> valueClass() {
 253         return valueLookup.lookupClass();
 254     }
 255 
 256     public Class<?> arrayValueClass() {
 257         return arrayValueClass(1);
 258     }
 259 
 260     public Class<?> arrayValueClass(int dims) {
 261         String dimsStr = "[[[[[[[[[[[[[[[[";
 262         if (dims < 1 || dims > 16) {
 263             throw new IllegalArgumentException("cannot create array class for dimension > 16");
 264         }
 265         String cn = dimsStr.substring(0, dims) + "Q" + valueClass().getName() + ";";
 266         return MinimalValueTypes_1_0.loadValueTypeClass(boxLookup.lookupClass(), cn);
 267     }
 268 
 269     public String toString() {
 270         return "ValueType boxClass=" + boxClass() + " valueClass=" + valueClass();
 271     }
 272 
 273     public MethodHandle defaultValueConstant() {
 274         ValueHandleKey key = ValueHandleKind.DEFAULT.key();
 275         return getOrLoad(boxLookup, key,
 276                 () -> MethodType.methodType(valueClass()),
 277                 C -> C.vdefault(valueClass()).vreturn());
 278     }
 279 
 280     public MethodHandle substitutabilityTest() {
 281         ValueHandleKey key = ValueHandleKind.EQ.key();
 282         return getOrLoad(valueLookup, key,
 283                 () -> MethodType.methodType(boolean.class, valueClass(), valueClass()),
 284                 C ->  substitutabilityTestBuilder(false, valueClass(), FieldInfo.stream(valueFields()), C));
 285     }
 286 
 287     private static <T extends MethodHandleCodeBuilder<T>> void substitutabilityTestBuilder(boolean needsInstanceCheck, Class<?> clazz, Stream<FieldInfo> fInfos, MethodHandleCodeBuilder<T> C) {
 288         if (needsInstanceCheck) {
 289             C.aload_1()
 290              .instanceof_(clazz)
 291              .emitCondJump(Opcode.IFEQ, CondKind.EQ, "fail")
 292              .aload_1()
 293              .checkcast(clazz)
 294              .store(1);
 295         }
 296         fInfos.forEach(fInfo -> {
 297             String fDesc = BytecodeDescriptor.unparse(fInfo.getType());
 298             if (fInfo.getType().isPrimitive()) {
 299                 //field is a primitive type - perform bytecode comparison
 300                 C.load(0).getfield(clazz, fInfo.getName(), fDesc);
 301                 C.load(1).getfield(clazz, fInfo.getName(), fDesc);
 302                 C.ifcmp(fDesc, CondKind.NE, "fail");
 303             } else if (MinimalValueTypes_1_0.isValueType(fInfo.getType())) {
 304                 //field is a value type - call subst handle recursively
 305                 C.load(0).getfield(clazz, fInfo.getName(), fDesc).dup().store(2);
 306                 valueHandleBuilder(fInfo.getType(), ValueHandleKind.EQ.key(), C)
 307                  .load(2)
 308                  .load(1).getfield(clazz, fInfo.getName(), fDesc)
 309                  .invokevirtual(MethodHandle.class, "invoke",
 310                          MethodType.methodType(boolean.class, fInfo.getType(), fInfo.getType()).toMethodDescriptorString(), false)
 311                  .const_(0).ifcmp(TypeTag.I, CondKind.EQ, "fail");
 312             } else {
 313                 //otherwise, field is a reference type, fallback to Objects.equals
 314                 C.load(0).getfield(clazz, fInfo.getName(), fDesc);
 315                 C.load(1).getfield(clazz, fInfo.getName(), fDesc);
 316                 C.invokestatic(Objects.class, "equals", "(Ljava/lang/Object;Ljava/lang/Object;)Z", false);
 317                 C.const_(0).ifcmp(TypeTag.I, CondKind.EQ, "fail");
 318             }
 319         });
 320         C.const_(1);
 321         C.ireturn();
 322         C.label("fail");
 323         C.const_(0);
 324         C.ireturn();
 325     }
 326 
 327     public MethodHandle substitutabilityHashCode() {
 328         ValueHandleKey key = ValueHandleKind.HASH.key();
 329         return getOrLoad(valueLookup, key,
 330                 () -> MethodType.methodType(int.class, valueClass()),
 331                 C -> substitutabilityHashCodeBuilder(valueClass(), FieldInfo.stream(valueFields()), C));
 332     }
 333 
 334     private static <T extends MethodHandleCodeBuilder<T>> void substitutabilityHashCodeBuilder(Class<?> clazz, Stream<FieldInfo> fInfos, MethodHandleCodeBuilder<T> C) {
 335         C.withLocal("res", "I");
 336         C.const_(1).store("res");
 337         fInfos.forEach(fInfo -> {
 338             String desc = BytecodeDescriptor.unparse(fInfo.getType());
 339             if (fInfo.getType().isPrimitive()) {
 340                 C.load(0).getfield(clazz, fInfo.getName(), desc);
 341                 C.invokestatic(Wrapper.asWrapperType(fInfo.getType()), "hashCode", "(" + desc + ")I", false);
 342             } else if (MinimalValueTypes_1_0.isValueType(fInfo.getType())) {
 343                 //field is a value type - call subst handle recursively
 344                 C.load(0).getfield(clazz, fInfo.getName(), desc).dup().store(2);
 345                 valueHandleBuilder(fInfo.getType(), ValueHandleKind.HASH.key(), C)
 346                  .load(2)
 347                  .invokevirtual(MethodHandle.class, "invoke",
 348                          MethodType.methodType(int.class, fInfo.getType()).toMethodDescriptorString(), false);
 349             } else {
 350                 C.load(0).getfield(clazz, fInfo.getName(), desc);
 351                 C.invokestatic(Objects.class, "hashCode", "(Ljava/lang/Object;)I", false);
 352             }
 353             C.load("res").const_(31).imul();
 354             C.iadd().store("res");
 355         });
 356         C.load("res").ireturn();
 357     }
 358 
 359     // ()Q
 360     public MethodHandle findConstructor(Lookup lookup, MethodType type) throws NoSuchMethodException, IllegalAccessException {
 361         return MethodHandles.filterReturnValue(lookup.findConstructor(boxClass(), type), unbox());
 362     }
 363 
 364     // (F1, ..., Fn)Q, fromDefault == true
 365     // (Q, F1, ..., Fn)Q, fromDefault == false
 366     public MethodHandle unreflectWithers(Lookup lookup,
 367                                          boolean fromDefault,
 368                                          Field... fields) throws NoSuchFieldException, IllegalAccessException {
 369         // Allow access if the lookup class is the VCC or DVT and the lookup
 370         // has private access
 371         Class<?> lc = lookup.lookupClass();
 372         if (!lookup.hasPrivateAccess() || (valueClass() != lc && boxClass() != lc)) {
 373             throw new IllegalAccessException(String.format("Class %s does not have vwithfield access to fields of %s",
 374                                                            lc.getName(), boxClass().getName()));
 375         }
 376 
 377         // Ensure fields are value component fields declared by the VCC
 378         for (Field f : fields) {
 379             if (!isValueField(f) || f.getDeclaringClass() != sourceClass()) {
 380                 throw new IllegalArgumentException(
 381                         String.format("Field %s is not a value component field declared in value capable class %s", f.getName(), sourceClass().getName()));
 382             }
 383         }
 384 
 385         ValueHandleKey key = ValueHandleKind.UNREFLECT_WITHERS.key(List.of(fromDefault,
 386                 FieldInfo.stream(fields).collect(Collectors.toList())));
 387         return getOrLoad(valueLookup, key,
 388                 () -> {
 389                     MethodType mt = MethodType.methodType(
 390                         valueClass(),
 391                         Stream.of(fields).map(Field::getType).toArray(Class[]::new));
 392 
 393                     if (!fromDefault) {
 394                         mt = mt.insertParameterTypes(0, valueClass());
 395                     }
 396                     return mt;
 397                 },
 398                 C -> {
 399                     int l = 0;
 400                     if (fromDefault) {
 401                         C.vdefault(valueClass());
 402                     } else {
 403                         C.load(0);
 404                         l = 1;
 405                     }
 406 
 407                     for (Field f : fields) {
 408                         String fType = BytecodeDescriptor.unparse(f.getType());
 409                         C.load(l).vwithfield(valueClass(), f.getName(), fType);
 410                         l += Wrapper.forBasicType(f.getType()).stackSlots();
 411                     }
 412                     C.vreturn();
 413                 });
 414     }
 415 
 416     // (Q, T)Q
 417     public MethodHandle findWither(Lookup lookup, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException {
 418         // Allow access if the lookup class is the VCC or DVT and the lookup
 419         // has private access
 420         Class<?> lc = lookup.lookupClass();
 421         if (!lookup.hasPrivateAccess() || (valueClass() != lc && boxClass() != lc)) {
 422             throw new IllegalAccessException(String.format("Class %s does not have vwithfield access to field %s.%s",
 423                                                            lc.getName(), boxClass().getName(), name));
 424         }
 425 
 426         // Check field exists on VCC
 427         lookup.findGetter(boxClass(), name, type);
 428 
 429         ValueHandleKey key = ValueHandleKind.WITHER.key(new FieldInfo(name, type));
 430         return getOrLoad(valueLookup, key,
 431                 () -> MethodType.methodType(valueClass(), valueClass(), type),
 432                 C -> C.vload(0).load(1).vwithfield(valueClass(), name, BytecodeDescriptor.unparse(type)).vreturn());
 433     }
 434 
 435     public MethodHandle unbox() {
 436         ValueHandleKey key = ValueHandleKind.UNBOX.key();
 437         return getOrLoad(boxLookup, key,
 438                 () -> MethodType.methodType(valueClass(), boxClass()),
 439                 C -> C.load(0).vunbox(valueClass()).vreturn());
 440     }
 441 
 442     public MethodHandle box() {
 443         ValueHandleKey key = ValueHandleKind.BOX.key();
 444         return getOrLoad(boxLookup, key,
 445                 () -> MethodType.methodType(boxClass(), valueClass()),
 446                 C -> C.vload(0).vbox(boxClass()).areturn());
 447     }
 448 
 449     public MethodHandle newArray() {
 450         ValueHandleKey key = ValueHandleKind.NEWARRAY.key();
 451         return getOrLoad(boxLookup, key,
 452                 () -> MethodType.methodType(arrayValueClass(), int.class),
 453                 C -> C.load(0).anewarray(valueClass()).areturn());
 454     }
 455 
 456     public MethodHandle arrayGetter() {
 457         ValueHandleKey key = ValueHandleKind.VALOAD.key();
 458         return getOrLoad(boxLookup, key,
 459                 () -> MethodType.methodType(valueClass(), arrayValueClass(), int.class),
 460                 C -> C.load(0).load(1).vaload().vreturn());
 461     }
 462 
 463     public MethodHandle arraySetter() {
 464         ValueHandleKey key = ValueHandleKind.VASTORE.key();
 465         return getOrLoad(boxLookup, key,
 466                 () -> MethodType.methodType(void.class, arrayValueClass(), int.class, valueClass()),
 467                 C -> C.load(0).load(1).load(2).vastore().return_());
 468     }
 469 
 470     public MethodHandle newMultiArray(int dims) {
 471         Class<?> arrayValueClass = arrayValueClass(dims);
 472         ValueHandleKey key = ValueHandleKind.MULTINEWARRAY.key(dims);
 473         return getOrLoad(boxLookup, key,
 474                 () -> {
 475                     Class<?>[] params = new Class<?>[dims];
 476                     Arrays.fill(params, int.class);
 477                     return MethodType.methodType(arrayValueClass, params);
 478                 },
 479                 C -> {
 480                     for (int i = 0 ; i < dims ; i++) {
 481                         C.load(i);
 482                     }
 483                     C.multianewarray(arrayValueClass, (byte)dims).areturn();
 484                 });
 485     }
 486 
 487     public MethodHandle arrayLength() {
 488         ValueHandleKey key = ValueHandleKind.ARRAYLENGTH.key();
 489         return getOrLoad(boxLookup, key,
 490                 () -> MethodType.methodType(int.class, arrayValueClass()),
 491                 C -> C.load(0).arraylength().ireturn());
 492     }
 493 
 494     public MethodHandle identity() {
 495         ValueHandleKey key = ValueHandleKind.IDENTITY.key();
 496         return getOrLoad(boxLookup, key,
 497                 () -> MethodType.methodType(valueClass(), valueClass()),
 498                 C -> C.vload(0).vreturn());
 499     }
 500 
 501     public MethodHandle findGetter(Lookup lookup, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException {
 502         //force access-check
 503         lookup.findGetter(boxClass(), name, type);
 504 
 505         ValueHandleKey key = ValueHandleKind.GETTER.key(new FieldInfo(name, type));
 506         String fieldType = BytecodeDescriptor.unparse(type);
 507         return getOrLoad(boxLookup, key,
 508                 () -> MethodType.methodType(type, valueClass()),
 509                 C -> C.vload(0).getfield(valueClass(), name, fieldType).return_(fieldType));
 510     }
 511 
 512     private static <T extends MethodHandleCodeBuilder<T>> T valueHandleBuilder(Class<?> dvt, ValueHandleKey key, MethodHandleCodeBuilder<T> C) {
 513         MethodType mt = key.kind.handleType();
 514         if (mt.parameterCount() > 0) {
 515             throw new AssertionError("Non-nilary handle builders not supported yet");
 516         }
 517         return C.vbox(MinimalValueTypes_1_0.getValueCapableClass(dvt))
 518                  .invokevirtual(Object.class, "getClass", "()Ljava/lang/Class;", false)
 519                  .invokestatic(ValueType.class, "forClass",
 520                          MethodType.methodType(ValueType.class, Class.class).toMethodDescriptorString(), false)
 521                  .invokevirtual(ValueType.class, key.kind.handleName(), key.kind.handleType().toMethodDescriptorString(), false);
 522     }
 523 
 524     private MethodHandle getOrLoad(Lookup lookup, ValueHandleKey key, Supplier<MethodType> typeSupplier, Consumer<? super MethodHandleCodeBuilder<?>> codeBuilder) {
 525         MethodHandle result = handleMap.get(key);
 526         if (result == null) {
 527             String handleDebugName = sourceClass().getName() + "_" + key.kind.handleName();
 528             result = MethodHandleBuilder.loadCode(lookup, handleDebugName, typeSupplier.get(), codeBuilder);
 529             handleMap.put(key, result);
 530         }
 531         return result;
 532     }
 533 
 534     boolean isValueField(Field f) {
 535         return (f.getModifiers() & (Modifier.FINAL | Modifier.STATIC)) == Modifier.FINAL;
 536     }
 537 
 538     public Field[] valueFields() {
 539         return Stream.of(sourceClass().getDeclaredFields())
 540                 .filter(this::isValueField)
 541                 .toArray(Field[]::new);
 542     }
 543 
 544     final static class FieldInfo {
 545 
 546         private final String name;
 547         private final Class<?> type;
 548 
 549         FieldInfo(String name, Class<?> type) {
 550             this.name = name;
 551             this.type = type;
 552         }
 553 
 554         String getName() { return name; }
 555         Class<?> getType() { return type; }
 556 
 557         @Override
 558         public boolean equals(Object o) {
 559             if (o instanceof FieldInfo) {
 560                 FieldInfo that = (FieldInfo)o;
 561                 return Objects.equals(name, that.name) &&
 562                         Objects.equals(type, that.type);
 563             } else {
 564                 return false;
 565             }
 566         }
 567 
 568         @Override
 569         public int hashCode() {
 570             return Objects.hash(name, type);
 571         }
 572 
 573         private static Stream<FieldInfo> stream(Field[] fields) {
 574            return Stream.of(fields).map(f -> new FieldInfo(f.getName(), f.getType()));
 575         }
 576 
 577         private static Stream<FieldInfo> stream(String[] fieldNames, Class<?>[] fieldTypes) {
 578             return IntStream.range(0, fieldNames.length)
 579                         .mapToObj(i -> new FieldInfo(fieldNames[i], fieldTypes[i]));
 580         }
 581     }
 582 }