< prev index next >

jdk/src/java.base/share/classes/valhalla/shady/ValueTypeHolder.java

Print this page




   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         },


  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         }


 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     }


 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);




   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         },


  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         }


 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     }


 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);


< prev index next >