Refactor ReflectionUtil (renamed class + changed a lot of stuff inside) and added support for accessing filtered field and changing final fields

This commit is contained in:
Marc Baloup 2021-12-27 17:53:17 +01:00
parent 7fc419086d
commit a82820cc15
5 changed files with 382 additions and 241 deletions

View File

@ -22,7 +22,7 @@ import com.mojang.brigadier.tree.LiteralCommandNode;
import fr.pandacube.lib.core.chat.ChatStatic; import fr.pandacube.lib.core.chat.ChatStatic;
import fr.pandacube.lib.core.commands.SuggestionsSupplier; import fr.pandacube.lib.core.commands.SuggestionsSupplier;
import fr.pandacube.lib.core.util.Log; import fr.pandacube.lib.core.util.Log;
import fr.pandacube.lib.core.util.ReflexionUtil; import fr.pandacube.lib.core.util.ReflectionUtil;
import net.md_5.bungee.BungeeCord; import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.api.CommandSender; import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.connection.ProxiedPlayer;
@ -204,7 +204,7 @@ public abstract class BrigadierCommand extends ChatStatic {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private static List<Suggestion> getSuggestionsFromSuggestionsBuilder(SuggestionsBuilder builder) { private static List<Suggestion> getSuggestionsFromSuggestionsBuilder(SuggestionsBuilder builder) {
try { try {
return (List<Suggestion>) ReflexionUtil.getDeclaredFieldValue(SuggestionsBuilder.class, builder, "result"); return (List<Suggestion>) ReflectionUtil.field("result").inClass(SuggestionsBuilder.class).getValue(builder);
} catch (ReflectiveOperationException e) { } catch (ReflectiveOperationException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }

View File

@ -19,7 +19,7 @@ import com.mojang.brigadier.tree.LiteralCommandNode;
import fr.pandacube.lib.core.chat.ChatStatic; import fr.pandacube.lib.core.chat.ChatStatic;
import fr.pandacube.lib.core.commands.SuggestionsSupplier; import fr.pandacube.lib.core.commands.SuggestionsSupplier;
import fr.pandacube.lib.core.util.Log; import fr.pandacube.lib.core.util.Log;
import fr.pandacube.lib.core.util.ReflexionUtil; import fr.pandacube.lib.core.util.ReflectionUtil;
public abstract class BrigadierCommand extends ChatStatic { public abstract class BrigadierCommand extends ChatStatic {
@ -128,7 +128,7 @@ public abstract class BrigadierCommand extends ChatStatic {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private static List<Suggestion> getSuggestionsFromSuggestionsBuilder(SuggestionsBuilder builder) { private static List<Suggestion> getSuggestionsFromSuggestionsBuilder(SuggestionsBuilder builder) {
try { try {
return (List<Suggestion>) ReflexionUtil.getDeclaredFieldValue(SuggestionsBuilder.class, builder, "result"); return (List<Suggestion>) ReflectionUtil.field("result").inClass(SuggestionsBuilder.class).getValue(builder);
} catch (ReflectiveOperationException e) { } catch (ReflectiveOperationException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }

View File

@ -0,0 +1,376 @@
package fr.pandacube.lib.core.util;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import io.github.classgraph.ClassGraph;
import io.github.classgraph.ScanResult;
import sun.misc.Unsafe;
public class ReflectionUtil {
private static final Unsafe sunMiscUnsafeInstance;
static {
try {
sunMiscUnsafeInstance = (Unsafe) field("theUnsafe")
.inClass(Unsafe.class)
.getStaticValue();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private record MethodCacheKey(Class<?> clazz, String methodName, List<Class<?>> parameters) { }
private static final Map<MethodCacheKey, Method> methodCache = new HashMap<>();
private record FieldCacheKey(Class<?> clazz, String fieldName) { };
private static final Map<FieldCacheKey, Field> fieldCache = new HashMap<>();
public static MethodReflectionBuilder method(String name, Class<?>... paramTypes) {
return new MethodReflectionBuilder(name, paramTypes);
}
public static FieldReflectionBuilder field(String name) {
return new FieldReflectionBuilder(name);
}
public static abstract class ReflectionBuilder {
Class<?> clazz;
String elementName;
protected ReflectionBuilder(String elementName) {
this.elementName = elementName;
}
public ReflectionBuilder inClass(Class<?> c) {
clazz = c;
return this;
}
public ReflectionBuilder inClass(String className) throws ClassNotFoundException {
return inClass(Class.forName(className));
}
public ReflectionBuilder inClassOfInstance(Object inst) {
if (inst == null)
throw new IllegalArgumentException("instance can't be null");
return inClass(inst.getClass());
}
}
public static class FieldReflectionBuilder extends ReflectionBuilder {
/* package */ FieldReflectionBuilder(String name) {
super(name);
}
// override method so their return type is more specific
public FieldReflectionBuilder inClass(Class<?> c) { return (FieldReflectionBuilder) super.inClass(c); }
public FieldReflectionBuilder inClass(String className) throws ClassNotFoundException { return (FieldReflectionBuilder) super.inClass(className); }
public FieldReflectionBuilder inClassOfInstance(Object inst) { return (FieldReflectionBuilder) super.inClassOfInstance(inst); }
public Field get() throws NoSuchFieldException {
return getDeclaredRecursively(clazz, elementName,
c -> c.getDeclaredField(elementName),
fieldCache, c -> new FieldCacheKey(c, elementName),
f -> f.setAccessible(true));
}
/* package */ Field getFiltered() throws NoSuchFieldException {
return ReflectionUtil.getFiltered(clazz, elementName, c -> c.getDeclaredField(elementName),
fieldCache, c -> new FieldCacheKey(c, elementName),
f -> f.setAccessible(true), f -> f.getName(),
"getDeclaredFields0", "copyField");
}
public Object getValue(Object instance) throws ReflectiveOperationException {
if (clazz == null && instance != null)
clazz = instance.getClass();
return get().get(instance);
}
public Object getStaticValue() throws ReflectiveOperationException {
return getValue(null);
}
public void setValue(Object instance, Object value) throws ReflectiveOperationException {
if (clazz == null && instance != null)
clazz = instance.getClass();
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) doesnt throw an exception
int modifiers = f.getModifiers();
if (Modifier.isFinal(modifiers)) {
field("modifiers").inClass(Field.class).getFiltered().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);
}
}
public static class MethodReflectionBuilder extends ReflectionBuilder {
Class<?>[] parameterTypes;
/* package */ MethodReflectionBuilder(String name, Class<?>... paramTypes) {
super(name);
parameterTypes = paramTypes == null ? new Class<?>[0] : paramTypes;
}
// override method so their return type is more specific
public MethodReflectionBuilder inClass(Class<?> c) { return (MethodReflectionBuilder) super.inClass(c); }
public MethodReflectionBuilder inClass(String className) throws ClassNotFoundException { return (MethodReflectionBuilder) super.inClass(className); }
public MethodReflectionBuilder inClassOfInstance(Object inst) { return (MethodReflectionBuilder) super.inClassOfInstance(inst); }
public Method get() throws NoSuchMethodException {
List<Class<?>> parameterTypesList = Arrays.asList(parameterTypes);
return getDeclaredRecursively(clazz, elementName,
c -> c.getDeclaredMethod(elementName, parameterTypes),
methodCache, c -> new MethodCacheKey(c, elementName, parameterTypesList),
m -> m.setAccessible(true));
}
/* package */ Method getFiltered() throws NoSuchMethodException {
List<Class<?>> parameterTypesList = Arrays.asList(parameterTypes);
return ReflectionUtil.getFiltered(clazz, elementName, c -> c.getDeclaredMethod(elementName, parameterTypes),
methodCache, c -> new MethodCacheKey(c, elementName, parameterTypesList),
m -> m.setAccessible(true), m -> m.getName(),
"getDeclaredMethods0", "copyMethod");
}
public Object invoke(Object instance, Object... values) throws ReflectiveOperationException {
if (clazz == null && instance != null)
clazz = instance.getClass();
return get().invoke(instance, values);
}
public Object invokeStatic(Object... values) throws ReflectiveOperationException {
return invoke(null, values);
}
}
private interface GetDeclaredFunction<T, E extends ReflectiveOperationException> {
public T get(Class<?> clazz) throws E;
}
private static <T, E extends ReflectiveOperationException, K> T getDeclaredRecursively(
Class<?> clazz, String name, GetDeclaredFunction<T, E> func,
Map<K, T> cache, Function<Class<?>, K> cacheKeySupplier, Consumer<T> setAccessible) throws E {
Objects.requireNonNull(clazz, "Class instance not provided");
Objects.requireNonNull(name, "Field name not provided");
E ex = null;
List<Class<?>> passedClasses = new ArrayList<>();
Class<?> currentClass = clazz;
T el = null;
do {
// check cache first
el = cache.get(cacheKeySupplier.apply(currentClass));
if (el != null)
break;
passedClasses.add(currentClass);
try {
el = func.get(currentClass);
break; // we found the field
} catch (ReflectiveOperationException e) {
if (ex == null) {
@SuppressWarnings("unchecked")
E ee = (E) e;
ex = ee;
}
}
currentClass = currentClass.getSuperclass();
} while(currentClass != null);
if (el == null)
throw ex;
// update cache
for (Class<?> c1 : passedClasses) {
cache.put(cacheKeySupplier.apply(c1), el);
}
setAccessible.accept(el);
return el;
}
/**
* Get a Field or Method of a class that is not accessible using {@link Class#getDeclaredField(String)}
* or using {@link Class#getDeclaredMethod(String, Class...)} because the implementation of {@link Class}
* block its direct access.
*
* This method calls an internal method of {@link Class} to retrieve the full list of field or method, then
* search in the list for the requested element.
*/
private static <T, E extends ReflectiveOperationException, K> T getFiltered(
Class<?> clazz, String name, GetDeclaredFunction<T, E> func,
Map<K, T> cache, Function<Class<?>, K> cacheKeySupplier,
Consumer<T> setAccessible, Function<T, String> getName,
String privateMethodName, String copyMethodName) throws E {
Objects.requireNonNull(clazz, "Class instance not provided");
Objects.requireNonNull(name, "Field name not provided");
K cacheKey = cacheKeySupplier.apply(clazz);
T el = cache.get(cacheKey);
if (el != null)
return el;
E ex = null;
try {
el = func.get(clazz);
} catch (ReflectiveOperationException e) {
@SuppressWarnings("unchecked")
E ee = (E) e;
ex = ee;
}
if (el == null) {
try {
@SuppressWarnings("unchecked")
T[] elements = (T[]) method(privateMethodName, boolean.class).invoke(clazz, false);
for (T element : elements) {
if (name.equals(getName.apply(element))) {
// the values in the elements array have to be copied
// (using special private methods in reflection api) before using it
Object reflectionFactoryOfClazz = method("getReflectionFactory").invoke(clazz);
@SuppressWarnings("unchecked")
T copiedElement = (T) method(copyMethodName, element.getClass())
.inClassOfInstance(reflectionFactoryOfClazz)
.invoke(reflectionFactoryOfClazz, element);
el = copiedElement;
break;
}
}
} catch (ReflectiveOperationException e) {
if (ex != null)
ex.addSuppressed(e);
}
}
if (el == null)
throw ex;
setAccessible.accept(el);
cache.put(cacheKey, el);
return el;
}
private static Cache<Class<?>, List<Class<?>>> subClassesLists = CacheBuilder.newBuilder()
.expireAfterAccess(10, TimeUnit.MINUTES)
.build();
public static <E> List<Class<? extends E>> getAllSubclasses(Class<E> clazz) {
try {
@SuppressWarnings("unchecked")
List<Class<? extends E>> classes = (List<Class<? extends E>>) (List<?>) subClassesLists.get(clazz, () -> {
try (ScanResult scanResult = new ClassGraph().enableClassInfo().ignoreClassVisibility().scan()) {
return scanResult.getSubclasses(clazz.getName()).loadClasses();
}
});
return classes;
} catch(ExecutionException e) {
Log.severe(e);
return null;
}
}
}

View File

@ -1,235 +0,0 @@
package fr.pandacube.lib.core.util;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import io.github.classgraph.ClassGraph;
import io.github.classgraph.ScanResult;
public class ReflexionUtil {
public static Object getDeclaredFieldValue(Object instance, String fieldName) throws ReflectiveOperationException {
if (instance == null)
throw new IllegalArgumentException("instance can't be null");
return getDeclaredFieldValue(instance.getClass(), instance, fieldName);
}
public static Object getDeclaredFieldValue(String className, String fieldName) throws ReflectiveOperationException {
return getDeclaredFieldValue(Class.forName(className), null, fieldName);
}
public static Object getDeclaredFieldValue(String className, Object instance, String fieldName) throws ReflectiveOperationException {
return getDeclaredFieldValue(Class.forName(className), instance, fieldName);
}
public static Object getDeclaredFieldValue(Class<?> clazz, String fieldName) throws ReflectiveOperationException {
return getDeclaredFieldValue(clazz, null, fieldName);
}
public static Object getDeclaredFieldValue(Class<?> clazz, Object instance, String fieldName) throws ReflectiveOperationException {
Field f = clazz.getDeclaredField(fieldName);
f.setAccessible(true);
return f.get(instance);
}
public static Object getFieldValue(Object instance, String fieldName) throws ReflectiveOperationException {
if (instance == null)
throw new IllegalArgumentException("instance can't be null");
return getFieldValue(instance.getClass(), instance, fieldName);
}
public static Object getFieldValue(String className, String fieldName) throws ReflectiveOperationException {
return getFieldValue(Class.forName(className), null, fieldName);
}
public static Object getFieldValue(String className, Object instance, String fieldName) throws ReflectiveOperationException {
return getFieldValue(Class.forName(className), instance, fieldName);
}
public static Object getFieldValue(Class<?> clazz, String fieldName) throws ReflectiveOperationException {
return getFieldValue(clazz, null, fieldName);
}
public static Object getFieldValue(Class<?> clazz, Object instance, String fieldName) throws ReflectiveOperationException {
Field f = clazz.getField(fieldName);
f.setAccessible(true);
return f.get(instance);
}
public static void setDeclaredFieldValue(Object instance, String fieldName, Object value) throws ReflectiveOperationException {
if (instance == null)
throw new IllegalArgumentException("instance can't be null");
setDeclaredFieldValue(instance.getClass(), instance, fieldName, value);
}
public static void setDeclaredFieldValue(String className, String fieldName, Object value) throws ReflectiveOperationException {
setDeclaredFieldValue(Class.forName(className), null, fieldName, value);
}
public static void setDeclaredFieldValue(String className, Object instance, String fieldName, Object value) throws ReflectiveOperationException {
setDeclaredFieldValue(Class.forName(className), null, fieldName, value);
}
public static void setDeclaredFieldValue(Class<?> clazz, String fieldName, Object value) throws ReflectiveOperationException {
setDeclaredFieldValue(clazz, null, fieldName, value);
}
public static void setDeclaredFieldValue(Class<?> clazz, Object instance, String fieldName, Object value) throws ReflectiveOperationException {
Field f = clazz.getDeclaredField(fieldName);
f.setAccessible(true);
f.set(instance, value);
}
public static void setFieldValue(Object instance, String fieldName, Object value) throws ReflectiveOperationException {
if (instance == null)
throw new IllegalArgumentException("instance can't be null");
setFieldValue(instance.getClass(), instance, fieldName, value);
}
public static void setFieldValue(String className, String fieldName, Object value) throws ReflectiveOperationException {
setFieldValue(Class.forName(className), null, fieldName, value);
}
public static void setFieldValue(String className, Object instance, String fieldName, Object value) throws ReflectiveOperationException {
setFieldValue(Class.forName(className), null, fieldName, value);
}
public static void setFieldValue(Class<?> clazz, String fieldName, Object value) throws ReflectiveOperationException {
setFieldValue(clazz, null, fieldName, value);
}
public static void setFieldValue(Class<?> clazz, Object instance, String fieldName, Object value) throws ReflectiveOperationException {
Field f = clazz.getField(fieldName);
f.setAccessible(true);
f.set(instance, value);
}
public static Object invokeDeclaredMethod(Object instance, String methodName) throws ReflectiveOperationException {
if (instance == null)
throw new IllegalArgumentException("instance can't be null");
return invokeDeclaredMethod(instance, methodName, new Class<?>[0]);
}
public static Object invokeDeclaredMethod(Object instance, String methodName, Class<?>[] parameterTypes, Object... args) throws ReflectiveOperationException {
if (instance == null)
throw new IllegalArgumentException("instance can't be null");
return invokeDeclaredMethod(instance.getClass(), instance, methodName, parameterTypes, args);
}
public static Object invokeDeclaredMethod(String className, String methodName) throws ReflectiveOperationException {
return invokeDeclaredMethod(Class.forName(className), null, methodName, new Class<?>[0]);
}
public static Object invokeDeclaredMethod(String className, String methodName, Class<?>[] parameterTypes, Object... args) throws ReflectiveOperationException {
return invokeDeclaredMethod(Class.forName(className), null, methodName, parameterTypes, args);
}
public static Object invokeDeclaredMethod(String className, Object instance, String methodName) throws ReflectiveOperationException {
return invokeDeclaredMethod(Class.forName(className), instance, methodName, new Class<?>[0]);
}
public static Object invokeDeclaredMethod(String className, Object instance, String methodName, Class<?>[] parameterTypes, Object... args) throws ReflectiveOperationException {
return invokeDeclaredMethod(Class.forName(className), instance, methodName, parameterTypes, args);
}
public static Object invokeDeclaredMethod(Class<?> clazz, String methodName) throws ReflectiveOperationException {
return invokeDeclaredMethod(clazz, null, methodName, new Class<?>[0]);
}
public static Object invokeDeclaredMethod(Class<?> clazz, String methodName, Class<?>[] parameterTypes, Object... args) throws ReflectiveOperationException {
return invokeDeclaredMethod(clazz, null, methodName, parameterTypes, args);
}
public static Object invokeDeclaredMethod(Class<?> clazz, Object instance, String methodName) throws ReflectiveOperationException {
return invokeDeclaredMethod(clazz, instance, methodName, new Class<?>[0]);
}
public static Object invokeDeclaredMethod(Class<?> clazz, Object instance, String methodName, Class<?>[] parameterTypes, Object... args) throws ReflectiveOperationException {
Method m = clazz.getDeclaredMethod(methodName, parameterTypes);
m.setAccessible(true);
return m.invoke(instance, args);
}
public static Object invokeMethod(Object instance, String methodName) throws ReflectiveOperationException {
if (instance == null)
throw new IllegalArgumentException("instance can't be null");
return invokeMethod(instance, methodName, new Class<?>[0]);
}
public static Object invokeMethod(Object instance, String methodName, Class<?>[] parameterTypes, Object... args) throws ReflectiveOperationException {
if (instance == null)
throw new IllegalArgumentException("instance can't be null");
return invokeMethod(instance.getClass(), instance, methodName, parameterTypes, args);
}
public static Object invokeMethod(String className, String methodName) throws ReflectiveOperationException {
return invokeMethod(Class.forName(className), null, methodName, new Class<?>[0]);
}
public static Object invokeMethod(String className, String methodName, Class<?>[] parameterTypes, Object... args) throws ReflectiveOperationException {
return invokeMethod(Class.forName(className), null, methodName, parameterTypes, args);
}
public static Object invokeMethod(String className, Object instance, String methodName) throws ReflectiveOperationException {
return invokeMethod(Class.forName(className), instance, methodName, new Class<?>[0]);
}
public static Object invokeMethod(String className, Object instance, String methodName, Class<?>[] parameterTypes, Object... args) throws ReflectiveOperationException {
return invokeMethod(Class.forName(className), instance, methodName, parameterTypes, args);
}
public static Object invokeMethod(Class<?> clazz, String methodName) throws ReflectiveOperationException {
return invokeMethod(clazz, null, methodName, new Class<?>[0]);
}
public static Object invokeMethod(Class<?> clazz, String methodName, Class<?>[] parameterTypes, Object... args) throws ReflectiveOperationException {
return invokeMethod(clazz, null, methodName, parameterTypes, args);
}
public static Object invokeMethod(Class<?> clazz, Object instance, String methodName) throws ReflectiveOperationException {
return invokeMethod(clazz, instance, methodName, new Class<?>[0]);
}
public static Object invokeMethod(Class<?> clazz, Object instance, String methodName, Class<?>[] parameterTypes, Object... args) throws ReflectiveOperationException {
Method m = clazz.getMethod(methodName, parameterTypes);
m.setAccessible(true);
return m.invoke(instance, args);
}
private static Cache<Class<?>, List<Class<?>>> subClassesLists = CacheBuilder.newBuilder()
.expireAfterAccess(10, TimeUnit.MINUTES)
.build();
public static <E> List<Class<? extends E>> getAllSubclasses(Class<E> clazz) {
try {
@SuppressWarnings("unchecked")
List<Class<? extends E>> classes = (List<Class<? extends E>>) (List<?>) subClassesLists.get(clazz, () -> {
try (ScanResult scanResult = new ClassGraph().enableClassInfo().ignoreClassVisibility().scan()) {
return scanResult.getSubclasses(clazz.getName()).loadClasses();
}
});
return classes;
} catch(ExecutionException e) {
Log.severe(e);
return null;
}
}
}

View File

@ -15,7 +15,7 @@ import org.bukkit.plugin.IllegalPluginAccessException;
import org.bukkit.plugin.RegisteredListener; import org.bukkit.plugin.RegisteredListener;
import org.bukkit.scheduler.BukkitTask; import org.bukkit.scheduler.BukkitTask;
import fr.pandacube.lib.core.util.ReflexionUtil; import fr.pandacube.lib.core.util.ReflectionUtil;
import fr.pandacube.lib.paper.PandaLibPaper; import fr.pandacube.lib.paper.PandaLibPaper;
public class BukkitEvent { public class BukkitEvent {
@ -50,7 +50,7 @@ public class BukkitEvent {
public static List<Class<? extends Event>> getAllEventClasses() { public static List<Class<? extends Event>> getAllEventClasses() {
List<Class<? extends Event>> classes = ReflexionUtil.getAllSubclasses(Event.class); List<Class<? extends Event>> classes = ReflectionUtil.getAllSubclasses(Event.class);
classes.removeIf(e -> getHandlerList(e) == null); classes.removeIf(e -> getHandlerList(e) == null);
return classes; return classes;
} }