mirror of
https://github.com/GTNewHorizons/GTNHLib.git
synced 2026-11-13 17:04:22 +00:00
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:
1 parent
2b155c237c
commit
1f8b0f865b
5 files changed
+773
-5
No files matched your search
+14
-5
@@ -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()) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user