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