ofStatic() {
+ return of(null);
+ }
+}
diff --git a/common/reflective/src/main/java/nahara/common/reflective/Reflective.java b/common/reflective/src/main/java/nahara/common/reflective/Reflective.java
new file mode 100644
index 0000000..6178117
--- /dev/null
+++ b/common/reflective/src/main/java/nahara/common/reflective/Reflective.java
@@ -0,0 +1,105 @@
+package nahara.common.reflective;
+
+import java.lang.reflect.AccessibleObject;
+
+import nahara.common.reflective.impl.ReflectiveImpl;
+
+/**
+ * "Once upon a time, there was a small pond in a middle of the forest. The pond water is said to be
+ * precious and shiny, which means it is highly reflective. Nahara saw the pond, got extremely
+ * curious, then decided to get closer to it.
+ * Nahara stands still, staring at her reflection on the watery surface. "What is this all about?",
+ * "Why is the {@code PI} constant of universe becomes 4.0?", "Am I living in a simulation?", "Am I living
+ * in an illusion?", "Is this a dream?". Nahara begins questioning her existence as she is about to perform
+ * an act that no ones could have guessed: It's Java reflectin' time!"
+ *
+ * This is Nahara's Toolkit for Reflections, a simple tool for your toolbox so that you can perform
+ * Java Reflection operations without dealing with nasty exceptions (I'm looking at you,
+ * {@link ReflectiveOperationException}). While the main point of this is to avoid catching exceptions,
+ * {@link Reflective} contains methods that helps you shorten the time it takes to use reflection.
+ * First, some methods allows you to feed in multiple possible names for a field or method. This is
+ * to ensure no magic will happens in the production environment, especially in Fabric modding, where dev
+ * environment may have remapped names, but in prouction, it becomes "method_abcdef".
+ * Second, some methods allows you to pick the only method with matching signature. There can be always
+ * more than 1 constructor or method with same name, but method signatures can't be the same.
+ * Third, all {@link Handle}s and {@link Method}s will calls {@link AccessibleObject#setAccessible(boolean)}
+ * everytime you access the fields or methods. You don't have to set its accessibility everytime you need
+ * to, well, access them.
+ *
+ * Now that I've done talkin' about {@link Reflective}, let's talk about "Where did the person named 'Nahara'
+ * comes from?". It might seems weird that I'm talking to you using Javadocs, but I kinda want to make this
+ * as a little "easter egg".
+ * To say "it comes from my dream" wouldn't be entirely correct, but the whole idea is: every person should have
+ * someone to help with their job. But because you can't see Nahara in person (she isn't real), she decided to mail
+ * her toolbox to you, with the hope of improving your productivity. That's why "Nahara's Toolkit" was created: to
+ * help me write Java code faster and more enjoyable. And what is the result of enjoying? Being productive!
+ * I don't expect this little dumb repository gets some attentions, but if you are using Nahara's toolkit in your
+ * project, consider making an "issue" in the Issues tab of my GitHub special repository for profile card. Nahara
+ * would likes to know her toolkit is actually useful!
+ *
+ * @see #constructor(Class...)
+ * @see #field(Class, String...)
+ * @see #method(Class, Class[], String...)
+ * @see #getterSetter(Class, String[], String[])
+ */
+public interface Reflective {
+ /**
+ * Combine getter and setter into a single handle.
+ * @param type The output type.
+ * @param getter All possible names for getter. Nahara will choose the method that is appeared first
+ * in this array that also present in the class.
+ * @param setter All possible names for setter. Nahara will choose the method that is appeared first
+ * in this array that also present in the class.
+ * @return The handle.
+ */
+ public HandleFactory getterSetter(Class type, String[] getter, String[] setter);
+
+ /**
+ * Get a field as a handle.
+ * @param type The output type.
+ * @param names All possible names for the field.
+ * @return The handle.
+ */
+ public HandleFactory field(Class type, String... names);
+
+ /**
+ * Find a method with specified name(s).
+ * @param returnType The return type of method.
+ * @param arguments Arguments to check, or {@code null} to ignore checking arguments.
+ * @param names All possible names for the method.
+ * @return The method.
+ */
+ public MethodFactory method(Class returnType, Class>[] arguments, String... names);
+
+ /**
+ * Find a constructor with given types for all arguments.
+ * @param arguments Arguments to check.
+ * @return The constructor described as a method.
+ */
+ public Method constructor(Class>... arguments);
+
+ /**
+ * Obtain a {@link Reflective} instance of a class.
+ * @param clazz The class.
+ * @return The {@link Reflective} instance.
+ */
+ public static Reflective of(Class clazz) {
+ return new ReflectiveImpl<>(clazz);
+ }
+
+ public static Reflective> of(String className) {
+ try {
+ return of(Class.forName(className));
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException();
+ }
+ }
+
+ public static String[] names(String... names) {
+ return names;
+ }
+
+ public static Class>[] args(Class>... args) {
+ return args;
+ }
+}
diff --git a/common/reflective/src/main/java/nahara/common/reflective/impl/FieldHandleFactoryImpl.java b/common/reflective/src/main/java/nahara/common/reflective/impl/FieldHandleFactoryImpl.java
new file mode 100644
index 0000000..aa78aed
--- /dev/null
+++ b/common/reflective/src/main/java/nahara/common/reflective/impl/FieldHandleFactoryImpl.java
@@ -0,0 +1,53 @@
+package nahara.common.reflective.impl;
+
+import java.lang.reflect.Field;
+
+import nahara.common.reflective.Handle;
+import nahara.common.reflective.HandleFactory;
+
+public class FieldHandleFactoryImpl implements HandleFactory {
+ private Field field;
+
+ public FieldHandleFactoryImpl(Field field) {
+ this.field = field;
+ }
+
+ @Override
+ public Handle of(S obj) {
+ return new FieldHandleImpl<>(this, obj);
+ }
+
+ public static class FieldHandleImpl implements Handle {
+ private FieldHandleFactoryImpl factory;
+ private S obj;
+
+ public FieldHandleImpl(FieldHandleFactoryImpl factory, S obj) {
+ this.factory = factory;
+ this.obj = obj;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public T get() {
+ try {
+ factory.field.setAccessible(true);
+ T t = (T) factory.field.get(obj);
+ factory.field.setAccessible(false);
+ return t;
+ } catch (IllegalArgumentException | IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void set(T obj) {
+ try {
+ factory.field.setAccessible(true);
+ factory.field.set(this.obj, obj);
+ factory.field.setAccessible(false);
+ } catch (IllegalArgumentException | IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+}
diff --git a/common/reflective/src/main/java/nahara/common/reflective/impl/MethodFactoryImpl.java b/common/reflective/src/main/java/nahara/common/reflective/impl/MethodFactoryImpl.java
new file mode 100644
index 0000000..f4a00c6
--- /dev/null
+++ b/common/reflective/src/main/java/nahara/common/reflective/impl/MethodFactoryImpl.java
@@ -0,0 +1,52 @@
+package nahara.common.reflective.impl;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+
+import nahara.common.reflective.Method;
+import nahara.common.reflective.MethodFactory;
+
+public class MethodFactoryImpl implements MethodFactory {
+ private java.lang.reflect.Method method;
+ private Constructor constructor;
+
+ public MethodFactoryImpl(java.lang.reflect.Method method, Constructor constructor) {
+ this.method = method;
+ this.constructor = constructor;
+ }
+
+ @Override
+ public Method of(S obj) {
+ return new MethodImpl<>(this, obj);
+ }
+
+ public static class MethodImpl implements Method {
+ private MethodFactoryImpl factory;
+ private S obj;
+
+ public MethodImpl(MethodFactoryImpl factory, S obj) {
+ this.factory = factory;
+ this.obj = obj;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public T invoke(Object... args) {
+ try {
+ if (factory.constructor != null) {
+ factory.constructor.setAccessible(true);
+ T t = (T) factory.constructor.newInstance(args);
+ factory.constructor.setAccessible(false);
+ return t;
+ } else {
+ factory.method.setAccessible(true);
+ T t = (T) factory.method.invoke(obj, args);
+ factory.method.setAccessible(false);
+ return t;
+ }
+ } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | InstantiationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+}
diff --git a/common/reflective/src/main/java/nahara/common/reflective/impl/MethodHandleFactoryImpl.java b/common/reflective/src/main/java/nahara/common/reflective/impl/MethodHandleFactoryImpl.java
new file mode 100644
index 0000000..38662d5
--- /dev/null
+++ b/common/reflective/src/main/java/nahara/common/reflective/impl/MethodHandleFactoryImpl.java
@@ -0,0 +1,58 @@
+package nahara.common.reflective.impl;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import nahara.common.reflective.Handle;
+import nahara.common.reflective.HandleFactory;
+
+public class MethodHandleFactoryImpl implements HandleFactory {
+ private Method getter;
+ private Method setter;
+
+ public MethodHandleFactoryImpl(Method getter, Method setter) {
+ this.getter = getter;
+ this.setter = setter;
+ }
+
+ @Override
+ public Handle of(S obj) {
+ return new MethodHandleImpl<>(this, obj);
+ }
+
+ public static class MethodHandleImpl implements Handle {
+ private MethodHandleFactoryImpl factory;
+ private S obj;
+
+ public MethodHandleImpl(MethodHandleFactoryImpl factory, S obj) {
+ this.factory = factory;
+ this.obj = obj;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public T get() {
+ if (factory.getter == null) throw new RuntimeException("Handle does not link to a getter");
+ try {
+ factory.getter.setAccessible(true);
+ T t = (T) factory.getter.invoke(obj);
+ factory.getter.setAccessible(false);
+ return t;
+ } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void set(T obj) {
+ if (factory.getter == null) throw new RuntimeException("Handle does not link to a setter");
+ try {
+ factory.getter.setAccessible(true);
+ factory.setter.invoke(this.obj, obj);
+ factory.getter.setAccessible(false);
+ } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+}
diff --git a/common/reflective/src/main/java/nahara/common/reflective/impl/ReflectiveImpl.java b/common/reflective/src/main/java/nahara/common/reflective/impl/ReflectiveImpl.java
new file mode 100644
index 0000000..a7f454b
--- /dev/null
+++ b/common/reflective/src/main/java/nahara/common/reflective/impl/ReflectiveImpl.java
@@ -0,0 +1,115 @@
+package nahara.common.reflective.impl;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+import nahara.common.reflective.HandleFactory;
+import nahara.common.reflective.MethodFactory;
+import nahara.common.reflective.Reflective;
+
+public class ReflectiveImpl implements Reflective {
+ private Class clazz;
+
+ public ReflectiveImpl(Class clazz) {
+ this.clazz = clazz;
+ }
+
+ @Override
+ public HandleFactory getterSetter(Class type, String[] getter, String[] setter) {
+ Method mGetter = null, mSetter = null;
+ for (Method method : clazz.getDeclaredMethods()) {
+ if (!type.isAssignableFrom(method.getReturnType())) continue;
+
+ if (mGetter == null && getter != null && getter.length > 0) {
+ for (String n : getter) {
+ if (method.getName().equals(n)) {
+ mGetter = method;
+ break;
+ }
+ }
+ }
+
+ if (mSetter == null && setter != null && setter.length > 0) {
+ for (String n : setter) {
+ if (method.getName().equals(n)) {
+ mSetter = method;
+ break;
+ }
+ }
+ }
+
+ if (
+ (getter == null || getter.length == 0 || mGetter != null) &&
+ (setter == null || setter.length == 0 || mSetter != null)) break;
+ }
+
+ if (getter != null && getter.length > 0 && mGetter == null) {
+ throw new RuntimeException("Unable to find matching getter method in " + clazz + " (possible methods are " + String.join(", ", getter) + ")");
+ }
+
+ if (setter != null && setter.length > 0 && mSetter == null) {
+ throw new RuntimeException("Unable to find matching setter method in " + clazz + " (possible methods are " + String.join(", ", setter) + ")");
+ }
+
+ return new MethodHandleFactoryImpl<>(mGetter, mSetter);
+ }
+
+ @Override
+ public MethodFactory method(Class returnType, Class>[] arguments, String... names) {
+ outer: for (Method method : clazz.getDeclaredMethods()) {
+ if (!returnType.isAssignableFrom(method.getReturnType())) continue;
+
+ for (String n : names) {
+ if (method.getName().equals(n)) {
+ if (arguments != null) {
+ Class>[] params = method.getParameterTypes();
+ if (params.length != arguments.length) continue outer;
+
+ for (int i = 0; i < arguments.length; i++) {
+ Class> param = params[i];
+ Class> arg = arguments[i];
+ if (!param.isAssignableFrom(arg)) continue outer;
+ }
+ }
+
+ return new MethodFactoryImpl<>(method, null);
+ }
+ }
+ }
+
+ throw new RuntimeException("Unable to find matching field in " + clazz + " (possible fields are " + String.join(", ", names) + ")");
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public nahara.common.reflective.Method constructor(Class>... arguments) {
+ for (Constructor> constructor : clazz.getDeclaredConstructors()) {
+ Class>[] params = constructor.getParameterTypes();
+ if (params.length != arguments.length) continue;
+
+ for (int i = 0; i < arguments.length; i++) {
+ Class> param = params[i];
+ Class> arg = arguments[i];
+ if (!param.isAssignableFrom(arg)) continue;
+ }
+
+ return new MethodFactoryImpl(null, (Constructor) constructor).ofStatic();
+ }
+
+ throw new RuntimeException("Unable to find matching constructor in " + clazz);
+ }
+
+ @Override
+ public HandleFactory field(Class type, String... names) {
+ for (Field field : clazz.getDeclaredFields()) {
+ if (!type.isAssignableFrom(field.getType())) continue;
+
+ for (String n : names) {
+ if (field.getName().equals(n)) return new FieldHandleFactoryImpl<>(field);
+ }
+ }
+
+ throw new RuntimeException("Unable to find matching field in " + clazz + " (possible fields are " + String.join(", ", names) + ")");
+ }
+}
diff --git a/common/reflective/src/test/java/nahara/common/reflective/ReflectiveTest.java b/common/reflective/src/test/java/nahara/common/reflective/ReflectiveTest.java
new file mode 100644
index 0000000..affbac1
--- /dev/null
+++ b/common/reflective/src/test/java/nahara/common/reflective/ReflectiveTest.java
@@ -0,0 +1,30 @@
+package nahara.common.reflective;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+class ReflectiveTest {
+ @Test
+ void test() {
+ Reflective r = Reflective.of(SampleClass.class);
+ var ctorMethod = r.constructor(String.class, int.class);
+ var nameHandle = r.field(String.class, "name");
+ var ageHandle = r.field(int.class, "age");
+ var describeHandle = r.getterSetter(String.class, Reflective.names("describe"), null);
+ var describeMethod = r.method(String.class, Reflective.args(), "describe");
+
+ SampleClass john = new SampleClass("John", 42);
+ assertEquals("John is now 42 years old!", john.describe());
+
+ ageHandle.of(john).map(age -> age - 10);
+ assertEquals("John is now 32 years old!", john.describe());
+
+ nameHandle.of(john).set("Annie");
+ assertEquals("Annie is now 32 years old!", john.describe());
+ assertEquals("Annie is now 32 years old!", describeHandle.of(john).get());
+ assertEquals("Annie is now 32 years old!", describeMethod.of(john).invoke());
+
+ assertEquals("Albert is now 69 years old!", ctorMethod.invoke("Albert", 69).describe());
+ }
+}
diff --git a/common/reflective/src/test/java/nahara/common/reflective/SampleClass.java b/common/reflective/src/test/java/nahara/common/reflective/SampleClass.java
new file mode 100644
index 0000000..2e020f5
--- /dev/null
+++ b/common/reflective/src/test/java/nahara/common/reflective/SampleClass.java
@@ -0,0 +1,15 @@
+package nahara.common.reflective;
+
+public class SampleClass {
+ private String name;
+ private int age;
+
+ public SampleClass(String name, int age) {
+ this.name = name;
+ this.age = age;
+ }
+
+ public String describe() {
+ return name + " is now " + age + " years old!";
+ }
+}
diff --git a/settings.gradle b/settings.gradle
index 18bb57a..17c0769 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -9,6 +9,7 @@ plugins {
'common/localize',
'common/nbtstring',
'common/pipeline',
+ 'common/reflective',
'common/structures',
'common/tasks',