package fr.pandacube.lib.reflect; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.atomic.AtomicReference; import io.github.classgraph.ClassGraph; import io.github.classgraph.ClassGraphException; import io.github.classgraph.ClassInfoList; import io.github.classgraph.ScanResult; import sun.misc.Unsafe; public class Reflect { private static final Unsafe sunMiscUnsafeInstance; static { classCache = Collections.synchronizedMap(new HashMap<>()); try { sunMiscUnsafeInstance = (Unsafe) ofClass(Unsafe.class).field("theUnsafe") .getStaticValue(); } catch (Exception e) { throw new RuntimeException("Cannot access to " + Unsafe.class + ".theUnsafe value.", e); } } private static final Map, ReflectClass> classCache; @SuppressWarnings("unchecked") public static ReflectClass ofClass(Class clazz) { return (ReflectClass) classCache.computeIfAbsent(clazz, ReflectClass::new); } public static ReflectClass ofClass(String className) throws ClassNotFoundException { return ofClass(Class.forName(className)); } public static ReflectClass ofClassOfInstance(Object instance) { if (instance == null) throw new IllegalArgumentException("instance can't be null"); return ofClass(instance.getClass()); } private record MethodIdentifier(String methodName, Class[] parameters) { private MethodIdentifier { Objects.requireNonNull(methodName); parameters = (parameters == null) ? new Class[0] : parameters; } @Override public boolean equals(Object other) { return other instanceof MethodIdentifier o && o.methodName.equals(methodName) && Arrays.equals(o.parameters, parameters); } @Override public int hashCode() { return methodName.hashCode() ^ Arrays.hashCode(parameters); } } private record ConstructorIdentifier(Class[] parameters) { private ConstructorIdentifier { parameters = (parameters == null) ? new Class[0] : parameters; } @Override public boolean equals(Object other) { return other instanceof ConstructorIdentifier o && Arrays.equals(o.parameters, parameters); } @Override public int hashCode() { return Arrays.hashCode(parameters); } } public static class ReflectClass { private final Class clazz; private final Map> methodCache = Collections.synchronizedMap(new HashMap<>()); private final Map> constructorCache = Collections.synchronizedMap(new HashMap<>()); private final Map> fieldCache = Collections.synchronizedMap(new HashMap<>()); private ReflectClass(Class clazz) { this.clazz = clazz; } /** * Returns the class wrapped by this ReflectClass instance. */ public Class get() { return clazz; } private ReflectMethod method(MethodIdentifier key, boolean bypassFilter) throws NoSuchMethodException { ReflectMethod method = methodCache.get(key); if (method == null) { method = new ReflectMethod<>(this, key, bypassFilter); methodCache.put(key, method); } return method; } public ReflectMethod method(String name, Class... paramTypes) throws NoSuchMethodException { return method(new MethodIdentifier(name, paramTypes), false); } public ReflectMethod filteredMethod(String name, Class... paramTypes) throws NoSuchMethodException { return method(new MethodIdentifier(name, paramTypes), true); } private ReflectConstructor constructor(ConstructorIdentifier key, boolean bypassFilter) throws NoSuchMethodException { ReflectConstructor constructor = constructorCache.get(key); if (constructor == null) { constructor = new ReflectConstructor<>(this, key, bypassFilter); constructorCache.put(key, constructor); } return constructor; } public ReflectConstructor constructor(Class... paramTypes) throws NoSuchMethodException { return constructor(new ConstructorIdentifier(paramTypes), false); } public ReflectConstructor filteredConstructor(Class... paramTypes) throws NoSuchMethodException { return constructor(new ConstructorIdentifier(paramTypes), true); } private ReflectField field0(String name, boolean bypassFilter) throws NoSuchFieldException { ReflectField constructor = fieldCache.get(name); if (constructor == null) { constructor = new ReflectField<>(this, name, bypassFilter); fieldCache.put(name, constructor); } return constructor; } public ReflectField field(String name) throws NoSuchFieldException { return field0(name, false); } public ReflectField filteredField(String name) throws NoSuchFieldException { return field0(name, true); } private final AtomicReference>> cachedSubclasses = new AtomicReference<>(); /** * Get all subclasses of the current class, using the ClassGraph library. *

* If the returned list is not yet cached, or {@code forceUpdateCache} is true, then the cache is updated before * being returned. This may take some time. *

* The ClassGraph library scan all class files in the class path, then loads those which will be returned by * this method. * * @param forceUpdateCache to force the update of the cache, even if it already filled. * @return the list of all subclasses found in all loaded class loader. * @throws ClassGraphException if any of the worker threads throws an uncaught exception, or the scan was interrupted. (see {@link ClassGraph#scan}) * @throws IllegalArgumentException f an exception or error was thrown while trying to load any of the classes. (see {@link ClassInfoList#loadClasses()}) */ public List> getAllSubclasses(boolean forceUpdateCache) { synchronized (cachedSubclasses) { if (forceUpdateCache || cachedSubclasses.get() == null) { try (ScanResult scanResult = new ClassGraph().enableClassInfo().ignoreClassVisibility().scan()) { cachedSubclasses.set(scanResult.getSubclasses(clazz).loadClasses()); } } @SuppressWarnings("unchecked") List> ret = (List>) (List) cachedSubclasses.get(); return ret; } } } public static abstract class ReflectMember { protected final ReflectClass reflectClass; protected final ID identifier; protected final EL cached; protected ReflectMember(ReflectClass c, ID id, boolean bypassFilter) throws EX { reflectClass = c; identifier = id; cached = (bypassFilter) ? fetchFiltered() : fetch(); } protected EL fetch() throws EX { // get element in current class try { EL el = fetchFromClass(reflectClass.clazz); setAccessible(el); return el; } catch (ReflectiveOperationException e1) { @SuppressWarnings("unchecked") EX ex = (EX) e1; // get parent class Class superClass = reflectClass.clazz.getSuperclass(); if (superClass == null) throw ex; // get element in parent class (will do recursion) try { EL el = fetchFromReflectClass(ofClass(superClass)); setAccessible(el); return el; } catch (ReflectiveOperationException e2) { ex.addSuppressed(e2); throw ex; } } } protected EL fetchFiltered() throws EX { // get element in current class try { EL el = fetchFromClass(reflectClass.clazz); setAccessible(el); return el; } catch (ReflectiveOperationException e1) { @SuppressWarnings("unchecked") EX ex = (EX) e1; // trying to bypass filtered member try { @SuppressWarnings("unchecked") EL[] elements = (EL[]) Reflect.ofClassOfInstance(reflectClass.clazz) .method(internalMethodNameElementArray(), boolean.class) .invoke(reflectClass.clazz, false); for (EL element : elements) { if (isEqualOurElement(element)) { // the values in the elements array have to be copied // (using special private methods in reflection api) before using it Object reflectionFactoryOfClazz = Reflect.ofClassOfInstance(reflectClass.clazz) .method("getReflectionFactory") .invoke(reflectClass.clazz); @SuppressWarnings("unchecked") EL copiedElement = (EL) Reflect.ofClassOfInstance(reflectionFactoryOfClazz) .method(internalMethodNameCopyElement(), element.getClass()) .invoke(reflectionFactoryOfClazz, element); setAccessible(copiedElement); return copiedElement; } } } catch (ReflectiveOperationException e2) { ex.addSuppressed(e2); } throw ex; } } protected abstract EL fetchFromClass(Class clazz) throws EX; protected abstract EL fetchFromReflectClass(ReflectClass rc) throws EX; protected abstract boolean isEqualOurElement(EL el); protected abstract String internalMethodNameElementArray(); protected abstract String internalMethodNameCopyElement(); protected abstract void setAccessible(EL el); public EL get() { return cached; } public abstract int getModifiers(); } public static class ReflectField extends ReflectMember { /* package */ ReflectField(ReflectClass c, String name, boolean bypassFilter) throws NoSuchFieldException { super(c, name, bypassFilter); } @Override protected Field fetchFromClass(Class clazz) throws NoSuchFieldException { return clazz.getDeclaredField(identifier); } @Override protected Field fetchFromReflectClass(ReflectClass rc) throws NoSuchFieldException { return rc.field(identifier).get(); } @Override protected boolean isEqualOurElement(Field el) { return identifier.equals(el.getName()); } @Override protected String internalMethodNameElementArray() { return "getDeclaredFields0"; } @Override protected String internalMethodNameCopyElement() { return "copyField"; } @Override protected void setAccessible(Field el) { el.setAccessible(true); } public Object getValue(Object instance) throws ReflectiveOperationException { return get().get(instance); } public Object getStaticValue() throws ReflectiveOperationException { return getValue(null); } public void setValue(Object instance, Object value) throws ReflectiveOperationException { Field f = get(); if (Modifier.isFinal(f.getModifiers())) { // if the field is final, we have to do some unsafe stuff :/ if (sunMiscUnsafeInstance != null) { // Java >= 16 // set the value of the field, directly in the memory if (Modifier.isStatic(f.getModifiers())) { long offset = sunMiscUnsafeInstance.staticFieldOffset(f); sunMiscUnsafeInstance.putObject(sunMiscUnsafeInstance.staticFieldBase(f), offset, value); } else { long offset = sunMiscUnsafeInstance.objectFieldOffset(f); sunMiscUnsafeInstance.putObject(instance, offset, value); } } else { // Java < 16 // change the modifier in the Field instance so the method #set(instance, value) doesn’t throw an exception int modifiers = f.getModifiers(); if (Modifier.isFinal(modifiers)) { ofClass(Field.class).field("modifiers").fetchFiltered().set(f, modifiers & ~Modifier.FINAL); } f.set(instance, value); } } else { // not final value f.set(instance, value); } } public void setStaticValue(Object value) throws ReflectiveOperationException { setValue(null, value); } @Override public int getModifiers() { return get().getModifiers(); } } public static class ReflectMethod extends ReflectMember { /* package */ ReflectMethod(ReflectClass c, MethodIdentifier methodId, boolean bypassFilter) throws NoSuchMethodException { super(c, methodId, bypassFilter); } @Override protected Method fetchFromClass(Class clazz) throws NoSuchMethodException { return clazz.getDeclaredMethod(identifier.methodName, identifier.parameters); } @Override protected Method fetchFromReflectClass(ReflectClass rc) throws NoSuchMethodException { return rc.method(identifier, false).get(); } @Override protected boolean isEqualOurElement(Method el) { return identifier.methodName.equals(el.getName()) && Arrays.equals(identifier.parameters, el.getParameterTypes()); } @Override protected String internalMethodNameElementArray() { return "getDeclaredMethods0"; } @Override protected String internalMethodNameCopyElement() { return "copyMethod"; } @Override protected void setAccessible(Method el) { el.setAccessible(true); } public Object invoke(Object instance, Object... values) throws ReflectiveOperationException { return get().invoke(instance, values); } public Object invokeStatic(Object... values) throws ReflectiveOperationException { return invoke(null, values); } @Override public int getModifiers() { return get().getModifiers(); } } public static class ReflectConstructor extends ReflectMember, NoSuchMethodException> { /* package */ ReflectConstructor(ReflectClass c, ConstructorIdentifier constructorId, boolean bypassFilter) throws NoSuchMethodException { super(c, constructorId, bypassFilter); } // Override since we don't want to recursively search for a constructor @Override protected Constructor fetch() throws NoSuchMethodException { Constructor el = fetchFromClass(reflectClass.clazz); setAccessible(el); return el; } @Override protected Constructor fetchFromClass(Class clazz) throws NoSuchMethodException { return clazz.getDeclaredConstructor(identifier.parameters); } @Override protected Constructor fetchFromReflectClass(ReflectClass rc) { throw new UnsupportedOperationException(); } @Override protected boolean isEqualOurElement(Constructor el) { return Arrays.equals(identifier.parameters, el.getParameterTypes()); } @Override protected String internalMethodNameElementArray() { return "getDeclaredConstructors0"; } @Override protected String internalMethodNameCopyElement() { return "copyConstructor"; } @Override protected void setAccessible(Constructor el) { el.setAccessible(true); } public T instanciate(Object... values) throws ReflectiveOperationException { return get().newInstance(values); } @Override public int getModifiers() { return get().getModifiers(); } } }