Add (final static and others) field reflection utilities, compatible with Java 8-19 (#17)

* Update buildscript

* Add (final static and others) field reflection utilities, compatible with Java 8-19

* Fix not throwing Throwables.propagate()
This commit is contained in:
Raven Szewczyk authored and GitHub committed 2023-01-23 16:49:00 +01:00
1 parent 2b155c237c
commit 1f8b0f865b
5 files changed
+773 -5

No files matched your search

+14 -5
View File
@@ -1,4 +1,4 @@
//version: 1671313514
//version: 1673027205
/*
DO NOT CHANGE THIS FILE!
Also, you may replace this file at any time if there is an update available.
@@ -319,9 +319,17 @@ if (file('addon.gradle').exists()) {
apply from: 'repositories.gradle'
configurations {
implementation.extendsFrom(shadowImplementation) // TODO: remove after all uses are refactored
implementation.extendsFrom(shadowCompile)
implementation.extendsFrom(shadeCompile)
// TODO: remove Compile after all uses are refactored to Implementation
for (config in [
shadowImplementation,
shadowCompile,
shadeCompile
]) {
compileClasspath.extendsFrom(config)
runtimeClasspath.extendsFrom(config)
testCompileClasspath.extendsFrom(config)
testRuntimeClasspath.extendsFrom(config)
}
}
repositories {
@@ -350,7 +358,8 @@ dependencies {
annotationProcessor('com.google.code.gson:gson:2.8.6')
annotationProcessor('com.gtnewhorizon:gtnhmixins:2.1.3:processor')
if (usesMixinDebug.toBoolean()) {
runtimeOnly('org.jetbrains:intellij-fernflower:1.2.1.16')
runtimeClasspath('org.jetbrains:intellij-fernflower:1.2.1.16')
testRuntimeClasspath('org.jetbrains:intellij-fernflower:1.2.1.16')
}
}
if (usesMixins.toBoolean() || forceEnableMixins.toBoolean()) {
+11
View File
@@ -1,5 +1,16 @@
// Add your dependencies here
dependencies {
testImplementation(platform("org.junit:junit-bom:5.9.2"))
testImplementation("org.junit.jupiter:junit-jupiter")
testRuntimeOnly("org.junit.platform:junit-platform-launcher") {
because("Only needed to run tests in a version of IntelliJ IDEA that bundles older versions")
}
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
testRuntimeOnly("org.junit.vintage:junit-vintage-engine")
}
test {
useJUnitPlatform()
}
@@ -0,0 +1,387 @@
package com.gtnewhorizon.gtnhlib.reflect;
import com.google.common.base.Throwables;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Modifier;
import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.Function;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import sun.misc.Unsafe;
/**
* Utilities for {@link java.lang.reflect.Field} reflection, compatible with Java 8-19.
* Can read and write to final static fields unlike the regular reflection API.
*/
public class Fields {
private static final Unsafe UNSAFE;
private static final MethodHandles.Lookup mhLookup = MethodHandles.lookup();
private Fields() {}
static {
try {
java.lang.reflect.Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
UNSAFE = (Unsafe) theUnsafe.get(null);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
/**
* How to look up a field in the class?
*/
public enum LookupType {
/** Look at the public API (using {@link Class#getField(String)}) */
PUBLIC,
/** Look at the fields declared in the exact class specified (using {@link Class#getDeclaredField(String)}) */
DECLARED,
/** Like {@link LookupType#DECLARED}, but also look in superclasses */
DECLARED_IN_HIERARCHY;
/**
* Executes the lookup strategy.
*/
public java.lang.reflect.Field lookup(Class<?> klass, String name) {
switch (this) {
case PUBLIC:
try {
return klass.getField(name);
} catch (NoSuchFieldException e) {
return null;
}
case DECLARED:
try {
return klass.getDeclaredField(name);
} catch (NoSuchFieldException e) {
return null;
}
case DECLARED_IN_HIERARCHY:
Class<?> currentClass = klass;
while (currentClass != null && currentClass != Object.class) {
try {
return currentClass.getDeclaredField(name);
} catch (NoSuchFieldException e) {
currentClass = currentClass.getSuperclass();
}
}
break;
}
return null;
}
}
/**
* Creates a type-safe fields accessor for the given class.
*/
@Nonnull
public static <T> ClassFields<T> ofClass(@Nonnull Class<T> klass) {
return new ClassFields<>(Objects.requireNonNull(klass));
}
public static class ClassFields<C> {
public final Class<C> klass;
private ClassFields(Class<C> klass) {
this.klass = klass;
}
private java.lang.reflect.Field getCheckedFieldImpl(
@Nonnull LookupType strategy, @Nonnull String name, @Nullable Class<?> expectedType) {
java.lang.reflect.Field javaField = strategy.lookup(klass, name);
if (javaField == null) {
return null;
}
if (expectedType != null) {
Class<?> realType = javaField.getType();
if (!realType.equals(expectedType)) {
throw new ClassCastException(String.format(
"Trying to access field %s#%s of type %s as type %s",
klass.getName(), name, realType.getName(), expectedType.getName()));
}
}
return javaField;
}
/**
* Looks up a field in the class using the reflection API and provides non-type-safe accessors to it.
* @param strategy The lookup strategy, see {@link LookupType}
* @param name Name of the field we are searching for
* @return A field wrapper for the found field, or null if not found.
*/
public Field<?> getUntypedField(@Nonnull LookupType strategy, @Nonnull String name) {
java.lang.reflect.Field javaField =
getCheckedFieldImpl(Objects.requireNonNull(strategy), Objects.requireNonNull(name), null);
if (javaField == null) {
return null;
}
return new Field<>(javaField, Object.class);
}
/**
* Looks up a field in the class using the reflection API and provides type-safe accessors to it.
* @param strategy The lookup strategy, see {@link LookupType}
* @param name Name of the field we are searching for
* @param expectedType The type we want to access the field as
* @return A field wrapper for the found field, or null if not found.
* @throws ClassCastException If the field exists under the given name, but is not of the given type.
*/
public <F> Field<F> getField(
@Nonnull LookupType strategy, @Nonnull String name, @Nonnull Class<F> expectedType) {
java.lang.reflect.Field javaField = getCheckedFieldImpl(
Objects.requireNonNull(strategy),
Objects.requireNonNull(name),
Objects.requireNonNull(expectedType));
if (javaField == null) {
return null;
}
return new Field<>(javaField, expectedType);
}
/**
* Looks up a primitive field in the class.
* @see ClassFields#getField(LookupType, String, Class)
*/
public Field<Boolean> getBooleanField(@Nonnull LookupType strategy, @Nonnull String name) {
java.lang.reflect.Field javaField =
getCheckedFieldImpl(Objects.requireNonNull(strategy), Objects.requireNonNull(name), boolean.class);
if (javaField == null) {
return null;
}
return new Field<>(javaField, Boolean.class);
}
/**
* Looks up a primitive field in the class.
* @see ClassFields#getField(LookupType, String, Class)
*/
public Field<Byte> getByteField(@Nonnull LookupType strategy, @Nonnull String name) {
java.lang.reflect.Field javaField =
getCheckedFieldImpl(Objects.requireNonNull(strategy), Objects.requireNonNull(name), byte.class);
if (javaField == null) {
return null;
}
return new Field<>(javaField, Byte.class);
}
/**
* Looks up a primitive field in the class.
* @see ClassFields#getField(LookupType, String, Class)
*/
public Field<Short> getShortField(@Nonnull LookupType strategy, @Nonnull String name) {
java.lang.reflect.Field javaField =
getCheckedFieldImpl(Objects.requireNonNull(strategy), Objects.requireNonNull(name), short.class);
if (javaField == null) {
return null;
}
return new Field<>(javaField, Short.class);
}
/**
* Looks up a primitive field in the class.
* @see ClassFields#getField(LookupType, String, Class)
*/
public Field<Integer> getIntField(@Nonnull LookupType strategy, @Nonnull String name) {
java.lang.reflect.Field javaField =
getCheckedFieldImpl(Objects.requireNonNull(strategy), Objects.requireNonNull(name), int.class);
if (javaField == null) {
return null;
}
return new Field<>(javaField, Integer.class);
}
/**
* Looks up a primitive field in the class.
* @see ClassFields#getField(LookupType, String, Class)
*/
public Field<Long> getLongField(@Nonnull LookupType strategy, @Nonnull String name) {
java.lang.reflect.Field javaField =
getCheckedFieldImpl(Objects.requireNonNull(strategy), Objects.requireNonNull(name), long.class);
if (javaField == null) {
return null;
}
return new Field<>(javaField, Long.class);
}
/**
* Looks up a primitive field in the class.
* @see ClassFields#getField(LookupType, String, Class)
*/
public Field<Character> getCharField(@Nonnull LookupType strategy, @Nonnull String name) {
java.lang.reflect.Field javaField =
getCheckedFieldImpl(Objects.requireNonNull(strategy), Objects.requireNonNull(name), char.class);
if (javaField == null) {
return null;
}
return new Field<>(javaField, Character.class);
}
/**
* Looks up a primitive field in the class.
* @see ClassFields#getField(LookupType, String, Class)
*/
public Field<Float> getFloatField(@Nonnull LookupType strategy, @Nonnull String name) {
java.lang.reflect.Field javaField =
getCheckedFieldImpl(Objects.requireNonNull(strategy), Objects.requireNonNull(name), float.class);
if (javaField == null) {
return null;
}
return new Field<>(javaField, Float.class);
}
/**
* Looks up a primitive field in the class.
* @see ClassFields#getField(LookupType, String, Class)
*/
public Field<Double> getDoubleField(@Nonnull LookupType strategy, @Nonnull String name) {
java.lang.reflect.Field javaField =
getCheckedFieldImpl(Objects.requireNonNull(strategy), Objects.requireNonNull(name), double.class);
if (javaField == null) {
return null;
}
return new Field<>(javaField, Double.class);
}
public class Field<F> {
public final java.lang.reflect.Field javaField;
public final Class<F> accessType;
public final boolean isPrimitive, isStatic, isFinal;
private final Function<C, F> getAccessor;
private final BiConsumer<C, F> setAccessor;
private Field(java.lang.reflect.Field javaField, Class<F> accessType) {
javaField.setAccessible(true);
this.javaField = javaField;
this.accessType = accessType;
this.isPrimitive = javaField.getType().isPrimitive();
this.isStatic = Modifier.isStatic(javaField.getModifiers());
this.isFinal = Modifier.isFinal(javaField.getModifiers());
try {
// Generate getter
final MethodHandle getterHandle = mhLookup.unreflectGetter(this.javaField);
if (this.isStatic) {
final MethodHandle exactGetterHandle = getterHandle.asType(MethodType.methodType(Object.class));
this.getAccessor = obj -> {
try {
return (F) exactGetterHandle.invokeExact();
} catch (Throwable e) {
throw Throwables.propagate(e);
}
};
} else {
final MethodHandle exactGetterHandle =
getterHandle.asType(MethodType.methodType(Object.class, Object.class));
this.getAccessor = obj -> {
try {
return (F) exactGetterHandle.invokeExact((Object) obj);
} catch (Throwable e) {
throw Throwables.propagate(e);
}
};
}
// Generate setter
BiConsumer<C, F> setAccessor = null;
try { // Try the MethodHandle way first, fall back to unsafe is not possible (e.g. static final
// fields)
final MethodHandle setterHandle = mhLookup.unreflectSetter(javaField);
if (this.isStatic) {
final MethodHandle exactSetterHandle =
setterHandle.asType(MethodType.methodType(void.class, Object.class));
setAccessor = (obj, val) -> {
try {
exactSetterHandle.invokeExact((Object) val);
} catch (Throwable e) {
throw Throwables.propagate(e);
}
};
} else {
final MethodHandle exactSetterHandle =
setterHandle.asType(MethodType.methodType(void.class, Object.class, Object.class));
setAccessor = (obj, val) -> {
try {
exactSetterHandle.invokeExact((Object) obj, (Object) val);
} catch (Throwable e) {
throw Throwables.propagate(e);
}
};
}
} catch (IllegalAccessException e) {
// Unsafe fallback
final Object staticBase = isStatic ? UNSAFE.staticFieldBase(javaField) : null;
final long fieldOffset =
isStatic ? UNSAFE.staticFieldOffset(javaField) : UNSAFE.objectFieldOffset(javaField);
if (!isPrimitive) {
setAccessor = (obj, val) ->
UNSAFE.putObject(unsafeBaseHelper(staticBase, obj, val), fieldOffset, val);
} else {
final Class<?> fieldType = javaField.getType();
if (fieldType.equals(boolean.class)) {
setAccessor = (obj, val) -> UNSAFE.putBoolean(
unsafeBaseHelper(staticBase, obj, val), fieldOffset, (Boolean) val);
} else if (fieldType.equals(byte.class)) {
setAccessor = (obj, val) ->
UNSAFE.putByte(unsafeBaseHelper(staticBase, obj, val), fieldOffset, (Byte) val);
} else if (fieldType.equals(short.class)) {
setAccessor = (obj, val) -> UNSAFE.putShort(
unsafeBaseHelper(staticBase, obj, val), fieldOffset, (Short) val);
} else if (fieldType.equals(int.class)) {
setAccessor = (obj, val) -> UNSAFE.putInt(
unsafeBaseHelper(staticBase, obj, val), fieldOffset, (Integer) val);
} else if (fieldType.equals(long.class)) {
setAccessor = (obj, val) ->
UNSAFE.putLong(unsafeBaseHelper(staticBase, obj, val), fieldOffset, (Long) val);
} else if (fieldType.equals(char.class)) {
setAccessor = (obj, val) -> UNSAFE.putChar(
unsafeBaseHelper(staticBase, obj, val), fieldOffset, (Character) val);
} else if (fieldType.equals(float.class)) {
setAccessor = (obj, val) -> UNSAFE.putFloat(
unsafeBaseHelper(staticBase, obj, val), fieldOffset, (Float) val);
} else if (fieldType.equals(double.class)) {
setAccessor = (obj, val) -> UNSAFE.putDouble(
unsafeBaseHelper(staticBase, obj, val), fieldOffset, (Double) val);
} else {
throw new IllegalStateException();
}
}
}
this.setAccessor = Objects.requireNonNull(setAccessor);
} catch (IllegalAccessException e) {
throw new RuntimeException(
"Couldn't create a Field accessor for " + klass.getName() + "#" + javaField.getName(), e);
}
}
private Object unsafeBaseHelper(Object staticBase, Object obj, Object value) {
if (!isStatic && !klass.isInstance(obj)) {
throw new ClassCastException("Illegal reflective access to field of " + klass
+ " using object of type " + obj.getClass());
}
if (!accessType.isAssignableFrom(value.getClass())) {
throw new ClassCastException("Illegal reflective set of value of type " + value.getClass()
+ " to field of type " + javaField.getType());
}
return isStatic ? staticBase : obj;
}
/**
* @param object Instance of the class, or null if accessing a static field
* @return The value of the field
*/
public F getValue(C object) {
return this.getAccessor.apply(object);
}
/**
* @param object Instance of the class, or null if accessing a static field
* @param value New value to set in the field
*/
public void setValue(C object, F value) {
this.setAccessor.accept(object, value);
}
}
}
}
@@ -0,0 +1,152 @@
package com.gtnewhorizon.gtnhlib.test.reflect;
public class FieldsTestSubject {
static {
// Prevent constant inlining by javac
staticFinalChar = Character.valueOf('0');
staticFinalBool = Boolean.valueOf(false);
staticFinalByte = Byte.valueOf((byte) 0).byteValue();
staticFinalShort = Short.valueOf((short) 0).shortValue();
staticFinalInt = Integer.valueOf(0).intValue();
staticFinalLong = Long.valueOf(0).longValue();
staticFinalFloat = Float.valueOf(0.0f).floatValue();
staticFinalDouble = Double.valueOf(0.0d).doubleValue();
staticFinalObject = String.format("in%s", "it");
}
private static final char staticFinalChar;
private static final boolean staticFinalBool;
private static final byte staticFinalByte;
private static final short staticFinalShort;
private static final int staticFinalInt;
private static final long staticFinalLong;
private static final float staticFinalFloat;
private static final double staticFinalDouble;
private static final String staticFinalObject;
private static char staticChar = '0';
private static boolean staticBool = false;
private static byte staticByte = 0;
private static short staticShort = 0;
private static int staticInt = 0;
private static long staticLong = 0;
private static float staticFloat = 0;
private static double staticDouble = 0;
private static String staticObject = "init";
private char dynamicChar = '0';
private boolean dynamicBool = false;
private byte dynamicByte = 0;
private short dynamicShort = 0;
private int dynamicInt = 0;
private long dynamicLong = 0;
private float dynamicFloat = 0;
private double dynamicDouble = 0;
private String dynamicObject = "init";
public static char getStaticFinalChar() {
return staticFinalChar;
}
public static boolean isStaticFinalBool() {
return staticFinalBool;
}
public static byte getStaticFinalByte() {
return staticFinalByte;
}
public static short getStaticFinalShort() {
return staticFinalShort;
}
public static int getStaticFinalInt() {
return staticFinalInt;
}
public static long getStaticFinalLong() {
return staticFinalLong;
}
public static float getStaticFinalFloat() {
return staticFinalFloat;
}
public static double getStaticFinalDouble() {
return staticFinalDouble;
}
public static String getStaticFinalObject() {
return staticFinalObject;
}
public static char getStaticChar() {
return staticChar;
}
public static boolean isStaticBool() {
return staticBool;
}
public static byte getStaticByte() {
return staticByte;
}
public static short getStaticShort() {
return staticShort;
}
public static int getStaticInt() {
return staticInt;
}
public static long getStaticLong() {
return staticLong;
}
public static float getStaticFloat() {
return staticFloat;
}
public static double getStaticDouble() {
return staticDouble;
}
public static String getStaticObject() {
return staticObject;
}
public char getDynamicChar() {
return dynamicChar;
}
public boolean isDynamicBool() {
return dynamicBool;
}
public byte getDynamicByte() {
return dynamicByte;
}
public short getDynamicShort() {
return dynamicShort;
}
public int getDynamicInt() {
return dynamicInt;
}
public long getDynamicLong() {
return dynamicLong;
}
public String getDynamicObject() {
return dynamicObject;
}
public float getDynamicFloat() {
return dynamicFloat;
}
public double getDynamicDouble() {
return dynamicDouble;
}
}
@@ -0,0 +1,209 @@
package com.gtnewhorizon.gtnhlib.test.reflect;
import static org.junit.jupiter.api.Assertions.*;
import com.gtnewhorizon.gtnhlib.reflect.Fields;
import org.junit.jupiter.api.Test;
public class FieldsTests {
@Test
void printJavaVersion() {
// For manual testing
String jvmVersion = System.getProperty("java.version");
System.err.println(jvmVersion);
}
@Test
void objectFields() {
final FieldsTestSubject subject = new FieldsTestSubject();
final Fields.ClassFields<FieldsTestSubject> classFields = Fields.ofClass(FieldsTestSubject.class);
final Fields.ClassFields<FieldsTestSubject>.Field<String> dynField =
classFields.getField(Fields.LookupType.DECLARED, "dynamicObject", String.class);
final Fields.ClassFields<FieldsTestSubject>.Field<String> staField =
classFields.getField(Fields.LookupType.DECLARED, "staticObject", String.class);
final Fields.ClassFields<FieldsTestSubject>.Field<String> finField =
classFields.getField(Fields.LookupType.DECLARED, "staticFinalObject", String.class);
assertAll(
() -> assertThrows(
ClassCastException.class,
() -> classFields.getField(Fields.LookupType.DECLARED, "dynamicObject", Long.class)),
() -> assertNull(classFields.getField(Fields.LookupType.DECLARED, "doesNotExist", Long.class)),
() -> assertNotNull(dynField),
() -> assertNotNull(staField),
() -> assertNotNull(finField));
assertAll(
() -> assertEquals("init", subject.getDynamicObject()),
() -> assertEquals("init", dynField.getValue(subject)),
() -> assertEquals("init", FieldsTestSubject.getStaticObject()),
() -> assertEquals("init", staField.getValue(null)),
() -> assertEquals("init", FieldsTestSubject.getStaticFinalObject()),
() -> assertEquals("init", finField.getValue(null)));
dynField.setValue(subject, "new1");
assertAll(
() -> assertEquals("new1", subject.getDynamicObject()),
() -> assertEquals("new1", dynField.getValue(subject)),
() -> assertEquals("init", FieldsTestSubject.getStaticObject()),
() -> assertEquals("init", staField.getValue(null)),
() -> assertEquals("init", FieldsTestSubject.getStaticFinalObject()),
() -> assertEquals("init", finField.getValue(null)));
staField.setValue(subject, "new2");
assertAll(
() -> assertEquals("new1", subject.getDynamicObject()),
() -> assertEquals("new1", dynField.getValue(subject)),
() -> assertEquals("new2", FieldsTestSubject.getStaticObject()),
() -> assertEquals("new2", staField.getValue(null)),
() -> assertEquals("init", FieldsTestSubject.getStaticFinalObject()),
() -> assertEquals("init", finField.getValue(null)));
finField.setValue(subject, "new3");
assertAll(
() -> assertEquals("new1", subject.getDynamicObject()),
() -> assertEquals("new1", dynField.getValue(subject)),
() -> assertEquals("new2", FieldsTestSubject.getStaticObject()),
() -> assertEquals("new2", staField.getValue(null)),
() -> assertEquals("new3", FieldsTestSubject.getStaticFinalObject()),
() -> assertEquals("new3", finField.getValue(null)));
// cleanup
staField.setValue(null, "init");
finField.setValue(null, "init");
}
@Test
@SuppressWarnings({"rawtypes", "unchecked"})
void objectUntypedFields() {
final FieldsTestSubject subject = new FieldsTestSubject();
final Fields.ClassFields classFields = Fields.ofClass(FieldsTestSubject.class);
final Fields.ClassFields.Field dynField =
classFields.getUntypedField(Fields.LookupType.DECLARED, "dynamicObject");
final Fields.ClassFields.Field staField =
classFields.getUntypedField(Fields.LookupType.DECLARED, "staticObject");
final Fields.ClassFields.Field finField =
classFields.getUntypedField(Fields.LookupType.DECLARED, "staticFinalObject");
assertAll(
() -> assertThrows(
ClassCastException.class,
() -> classFields.getField(Fields.LookupType.DECLARED, "dynamicObject", Long.class)),
() -> assertNull(classFields.getField(Fields.LookupType.DECLARED, "doesNotExist", Long.class)),
() -> assertNotNull(dynField),
() -> assertNotNull(staField),
() -> assertNotNull(finField));
assertAll(
() -> assertEquals("init", subject.getDynamicObject()),
() -> assertEquals("init", dynField.getValue(subject)),
() -> assertEquals("init", FieldsTestSubject.getStaticObject()),
() -> assertEquals("init", staField.getValue(null)),
() -> assertEquals("init", FieldsTestSubject.getStaticFinalObject()),
() -> assertEquals("init", finField.getValue(null)));
dynField.setValue(subject, "new1");
assertAll(
() -> assertEquals("new1", subject.getDynamicObject()),
() -> assertEquals("new1", dynField.getValue(subject)),
() -> assertEquals("init", FieldsTestSubject.getStaticObject()),
() -> assertEquals("init", staField.getValue(null)),
() -> assertEquals("init", FieldsTestSubject.getStaticFinalObject()),
() -> assertEquals("init", finField.getValue(null)));
staField.setValue(subject, "new2");
assertAll(
() -> assertEquals("new1", subject.getDynamicObject()),
() -> assertEquals("new1", dynField.getValue(subject)),
() -> assertEquals("new2", FieldsTestSubject.getStaticObject()),
() -> assertEquals("new2", staField.getValue(null)),
() -> assertEquals("init", FieldsTestSubject.getStaticFinalObject()),
() -> assertEquals("init", finField.getValue(null)));
finField.setValue(subject, "new3");
assertAll(
() -> assertEquals("new1", subject.getDynamicObject()),
() -> assertEquals("new1", dynField.getValue(subject)),
() -> assertEquals("new2", FieldsTestSubject.getStaticObject()),
() -> assertEquals("new2", staField.getValue(null)),
() -> assertEquals("new3", FieldsTestSubject.getStaticFinalObject()),
() -> assertEquals("new3", finField.getValue(null)));
// cleanup
staField.setValue(null, "init");
finField.setValue(null, "init");
}
@Test
void intFields() {
final FieldsTestSubject subject = new FieldsTestSubject();
final Fields.ClassFields<FieldsTestSubject> classFields = Fields.ofClass(FieldsTestSubject.class);
final Fields.ClassFields<FieldsTestSubject>.Field<Integer> dynField =
classFields.getIntField(Fields.LookupType.DECLARED, "dynamicInt");
final Fields.ClassFields<FieldsTestSubject>.Field<Integer> staField =
classFields.getIntField(Fields.LookupType.DECLARED, "staticInt");
final Fields.ClassFields<FieldsTestSubject>.Field<Integer> finField =
classFields.getIntField(Fields.LookupType.DECLARED, "staticFinalInt");
assertAll(
() -> assertThrows(
ClassCastException.class,
() -> classFields.getField(Fields.LookupType.DECLARED, "dynamicInt", Long.class)),
() -> assertThrows(
ClassCastException.class,
() -> classFields.getField(Fields.LookupType.DECLARED, "dynamicInt", Integer.class)),
() -> assertNull(classFields.getField(Fields.LookupType.DECLARED, "doesNotExist", Long.class)),
() -> assertNotNull(dynField),
() -> assertNotNull(staField),
() -> assertNotNull(finField));
assertAll(
() -> assertEquals(0, (int) subject.getDynamicInt()),
() -> assertEquals(0, (int) dynField.getValue(subject)),
() -> assertEquals(0, (int) FieldsTestSubject.getStaticInt()),
() -> assertEquals(0, (int) staField.getValue(null)),
() -> assertEquals(0, (int) FieldsTestSubject.getStaticFinalInt()),
() -> assertEquals(0, (int) finField.getValue(null)));
dynField.setValue(subject, 1);
assertAll(
() -> assertEquals(1, (int) subject.getDynamicInt()),
() -> assertEquals(1, (int) dynField.getValue(subject)),
() -> assertEquals(0, (int) FieldsTestSubject.getStaticInt()),
() -> assertEquals(0, (int) staField.getValue(null)),
() -> assertEquals(0, (int) FieldsTestSubject.getStaticFinalInt()),
() -> assertEquals(0, (int) finField.getValue(null)));
staField.setValue(subject, 2);
assertAll(
() -> assertEquals(1, (int) subject.getDynamicInt()),
() -> assertEquals(1, (int) dynField.getValue(subject)),
() -> assertEquals(2, (int) FieldsTestSubject.getStaticInt()),
() -> assertEquals(2, (int) staField.getValue(null)),
() -> assertEquals(0, (int) FieldsTestSubject.getStaticFinalInt()),
() -> assertEquals(0, (int) finField.getValue(null)));
finField.setValue(subject, 3);
assertAll(
() -> assertEquals(1, (int) subject.getDynamicInt()),
() -> assertEquals(1, (int) dynField.getValue(subject)),
() -> assertEquals(2, (int) FieldsTestSubject.getStaticInt()),
() -> assertEquals(2, (int) staField.getValue(null)),
() -> assertEquals(3, (int) FieldsTestSubject.getStaticFinalInt()),
() -> assertEquals(3, (int) finField.getValue(null)));
// cleanup
staField.setValue(null, 0);
finField.setValue(null, 0);
}
}