Skip to content

Commit

Permalink
Nahara's Toolkit Reflective: Reflection for mad lads!
Browse files Browse the repository at this point in the history
  • Loading branch information
nahkd123 committed Nov 9, 2023
1 parent c5967c8 commit dbb8b39
Show file tree
Hide file tree
Showing 13 changed files with 496 additions and 0 deletions.
18 changes: 18 additions & 0 deletions common/reflective/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
plugins {
}

eclipse {
project.name = "Nahara Toolkit - Common - Reflective"
}

repositories {
mavenCentral()
}

dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0'
}

tasks.named('test') {
useJUnitPlatform()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package nahara.common.reflective;

import java.util.function.Consumer;
import java.util.function.UnaryOperator;

/**
* <p>Handles allows you to get/set objects using either field accessing or getter and setter.</p>
* @param <T> The type.
*/
public interface Handle<T> {
public T get();
public void set(T obj);

default void apply(Consumer<Handle<T>> applier) {
applier.accept(this);
}

default void map(UnaryOperator<T> mapper) {
set(mapper.apply(get()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package nahara.common.reflective;

/**
* <p>The factory for getting the handle from given object.</p>
* @param <S> The object type.
* @param <T> The type for handle.
*/
public interface HandleFactory<S, T> {
public Handle<T> of(S obj);

default Handle<T> ofStatic() {
return of(null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package nahara.common.reflective;

public interface Method<T> {
public T invoke(Object... args);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package nahara.common.reflective;

public interface MethodFactory<S, T> {
public Method<T> of(S obj);

default Method<T> ofStatic() {
return of(null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package nahara.common.reflective;

import java.lang.reflect.AccessibleObject;

import nahara.common.reflective.impl.ReflectiveImpl;

/**
* <p><i>"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 <b>reflective</b>. Nahara saw the pond, got extremely
* curious, then decided to get closer to it.</i></p>
* <p><i>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!"</i></p>
*
* <p>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.</p>
* <p>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".</p>
* <p>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.</p>
* <p>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.</p>
*
* <p>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".</p>
* <p>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!</p>
* <p>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!</p>
*
* @see #constructor(Class...)
* @see #field(Class, String...)
* @see #method(Class, Class[], String...)
* @see #getterSetter(Class, String[], String[])
*/
public interface Reflective<S> {
/**
* <p>Combine getter and setter into a single handle.</p>
* @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 <T> HandleFactory<S, T> getterSetter(Class<T> type, String[] getter, String[] setter);

/**
* <p>Get a field as a handle.</p>
* @param type The output type.
* @param names All possible names for the field.
* @return The handle.
*/
public <T> HandleFactory<S, T> field(Class<T> type, String... names);

/**
* <p>Find a method with specified name(s).</p>
* @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 <T> MethodFactory<S, T> method(Class<T> returnType, Class<?>[] arguments, String... names);

/**
* <p>Find a constructor with given types for all arguments.</p>
* @param arguments Arguments to check.
* @return The constructor described as a method.
*/
public Method<S> constructor(Class<?>... arguments);

/**
* <p>Obtain a {@link Reflective} instance of a class.</p>
* @param clazz The class.
* @return The {@link Reflective} instance.
*/
public static <S> Reflective<S> of(Class<S> 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;
}
}
Original file line number Diff line number Diff line change
@@ -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<S, T> implements HandleFactory<S, T> {
private Field field;

public FieldHandleFactoryImpl(Field field) {
this.field = field;
}

@Override
public Handle<T> of(S obj) {
return new FieldHandleImpl<>(this, obj);
}

public static class FieldHandleImpl<S, T> implements Handle<T> {
private FieldHandleFactoryImpl<S, T> factory;
private S obj;

public FieldHandleImpl(FieldHandleFactoryImpl<S, T> 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);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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<S, T> implements MethodFactory<S, T> {
private java.lang.reflect.Method method;
private Constructor<T> constructor;

public MethodFactoryImpl(java.lang.reflect.Method method, Constructor<T> constructor) {
this.method = method;
this.constructor = constructor;
}

@Override
public Method<T> of(S obj) {
return new MethodImpl<>(this, obj);
}

public static class MethodImpl<S, T> implements Method<T> {
private MethodFactoryImpl<S, T> factory;
private S obj;

public MethodImpl(MethodFactoryImpl<S, T> 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);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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<S, T> implements HandleFactory<S, T> {
private Method getter;
private Method setter;

public MethodHandleFactoryImpl(Method getter, Method setter) {
this.getter = getter;
this.setter = setter;
}

@Override
public Handle<T> of(S obj) {
return new MethodHandleImpl<>(this, obj);
}

public static class MethodHandleImpl<S, T> implements Handle<T> {
private MethodHandleFactoryImpl<S, T> factory;
private S obj;

public MethodHandleImpl(MethodHandleFactoryImpl<S, T> 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);
}
}
}
}
Loading

0 comments on commit dbb8b39

Please sign in to comment.