diff --git a/Bungee/src/main/java/fr/pandacube/lib/bungee/commands/BrigadierCommand.java b/Bungee/src/main/java/fr/pandacube/lib/bungee/commands/BrigadierCommand.java index 5cf8660..955f5d4 100644 --- a/Bungee/src/main/java/fr/pandacube/lib/bungee/commands/BrigadierCommand.java +++ b/Bungee/src/main/java/fr/pandacube/lib/bungee/commands/BrigadierCommand.java @@ -22,7 +22,7 @@ import com.mojang.brigadier.tree.LiteralCommandNode; import fr.pandacube.lib.core.chat.ChatStatic; import fr.pandacube.lib.core.commands.SuggestionsSupplier; import fr.pandacube.lib.core.util.Log; -import fr.pandacube.lib.core.util.ReflectionUtil; +import fr.pandacube.lib.core.util.Reflect; import net.md_5.bungee.BungeeCord; import net.md_5.bungee.api.CommandSender; import net.md_5.bungee.api.connection.ProxiedPlayer; @@ -204,7 +204,7 @@ public abstract class BrigadierCommand extends ChatStatic { @SuppressWarnings("unchecked") private static List getSuggestionsFromSuggestionsBuilder(SuggestionsBuilder builder) { try { - return (List) ReflectionUtil.field("result").inClass(SuggestionsBuilder.class).getValue(builder); + return (List) Reflect.ofClass(SuggestionsBuilder.class).field("result").getValue(builder); } catch (ReflectiveOperationException e) { throw new RuntimeException(e); } diff --git a/CLI/src/main/java/fr/pandacube/lib/cli/BrigadierCommand.java b/CLI/src/main/java/fr/pandacube/lib/cli/BrigadierCommand.java index 87bd047..0917f1a 100644 --- a/CLI/src/main/java/fr/pandacube/lib/cli/BrigadierCommand.java +++ b/CLI/src/main/java/fr/pandacube/lib/cli/BrigadierCommand.java @@ -19,7 +19,7 @@ import com.mojang.brigadier.tree.LiteralCommandNode; import fr.pandacube.lib.core.chat.ChatStatic; import fr.pandacube.lib.core.commands.SuggestionsSupplier; import fr.pandacube.lib.core.util.Log; -import fr.pandacube.lib.core.util.ReflectionUtil; +import fr.pandacube.lib.core.util.Reflect; public abstract class BrigadierCommand extends ChatStatic { @@ -128,7 +128,7 @@ public abstract class BrigadierCommand extends ChatStatic { @SuppressWarnings("unchecked") private static List getSuggestionsFromSuggestionsBuilder(SuggestionsBuilder builder) { try { - return (List) ReflectionUtil.field("result").inClass(SuggestionsBuilder.class).getValue(builder); + return (List) Reflect.ofClass(SuggestionsBuilder.class).field("result").getValue(builder); } catch (ReflectiveOperationException e) { throw new RuntimeException(e); } diff --git a/Core/src/main/java/fr/pandacube/lib/core/util/Reflect.java b/Core/src/main/java/fr/pandacube/lib/core/util/Reflect.java new file mode 100644 index 0000000..947cb9a --- /dev/null +++ b/Core/src/main/java/fr/pandacube/lib/core/util/Reflect.java @@ -0,0 +1,411 @@ +package fr.pandacube.lib.core.util; + +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.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; +import java.util.function.Predicate; + +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 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; + + public static ReflectClass ofClass(Class clazz) { + return 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 != null && other instanceof MethodIdentifier o + && o.methodName.equals(methodName) + && Arrays.equals(o.parameters, parameters); + } + @Override + public int hashCode() { + return methodName.hashCode() ^ Arrays.hashCode(parameters); + } + } + + + + + + + public static class ReflectClass { + private Class clazz; + + private final Map methodCache = Collections.synchronizedMap(new HashMap<>()); + private final Map fieldCache = Collections.synchronizedMap(new HashMap<>()); + + private ReflectClass(Class clazz) { + this.clazz = clazz; + } + + private ReflectMethod method(MethodIdentifier key) { + return methodCache.computeIfAbsent(key, k -> new ReflectMethod(this, k)); + } + + public ReflectMethod method(String name, Class... paramTypes) { + return method(new MethodIdentifier(name, paramTypes)); + } + + public ReflectField field(String name) { + return fieldCache.computeIfAbsent(name, n -> new ReflectField(this, n)); + } + } + + + + + + + + + + + + + + + + + + + + + + + + public static abstract class ReflectClassEl { + ReflectClass reflectClass; + String elementName; + + protected ReflectClassEl(ReflectClass c, String elementName) { + reflectClass = c; + this.elementName = elementName; + } + + } + + public static class ReflectField extends ReflectClassEl { + + private Field cached, cachedFiltered; + + /* package */ ReflectField(ReflectClass c, String name) { + super(c, name); + } + + public synchronized Field get() throws NoSuchFieldException { + if (cached == null) { + cached = getDeclaredRecursively(reflectClass.clazz, + c -> c.getDeclaredField(elementName), + c -> ofClass(c).field(elementName).get(), + f -> f.setAccessible(true)); + } + return cached; + } + + /* package */ synchronized Field getFiltered() throws NoSuchFieldException { + if (cachedFiltered == null) { + cachedFiltered = Reflect.getFiltered(reflectClass.clazz, + c -> c.getDeclaredField(elementName), + f -> f.setAccessible(true), + f -> elementName.equals(f.getName()), + "getDeclaredFields0", "copyField"); + } + return cachedFiltered; + } + + 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").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 ReflectMethod extends ReflectClassEl { + + private Method cached, cachedFiltered; + + MethodIdentifier methodId; + Class[] parameterTypes; + + /* package */ ReflectMethod(ReflectClass c, MethodIdentifier methodId) { + super(c, methodId.methodName); + this.methodId = methodId; + parameterTypes = methodId.parameters; + } + + + public Method get() throws NoSuchMethodException { + if (cached == null) { + cached = getDeclaredRecursively(reflectClass.clazz, + c -> c.getDeclaredMethod(elementName, parameterTypes), + c -> ofClass(c).method(methodId).get(), + m -> m.setAccessible(true)); + } + return cached; + } + + /* package */ Method getFiltered() throws NoSuchMethodException { + if (cachedFiltered == null) { + cachedFiltered = Reflect.getFiltered(reflectClass.clazz, + c -> c.getDeclaredMethod(elementName, parameterTypes), + m -> m.setAccessible(true), + m -> elementName.equals(m.getName()) && Arrays.equals(parameterTypes, m.getParameterTypes()), + "getDeclaredMethods0", "copyMethod"); + } + return cachedFiltered; + } + + 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); + } + } + + + + + + + + + + + + + private interface GetReflectiveElement { + public T get(Class clazz) throws E; + } + + private static T getDeclaredRecursively( + Class clazz, GetReflectiveElement jlrGetter, + GetReflectiveElement parentGetter, Consumer setAccessible) throws E { + Objects.requireNonNull(clazz, "Class instance not provided"); + + E ex = null; + T el = null; + + // get element in provided class + try { + el = jlrGetter.get(clazz); + setAccessible.accept(el); + } catch (ReflectiveOperationException e) { + if (ex == null) { + @SuppressWarnings("unchecked") + E ee = (E) e; + ex = ee; + } + } + + // get element in parent class (will do recursion) + if (el == null) { + try { + el = parentGetter.get(clazz.getSuperclass()); + } catch (ReflectiveOperationException e) { + if (ex == null) { + @SuppressWarnings("unchecked") + E ee = (E) e; + ex = ee; + } + else { + ex.addSuppressed(e); + } + } + } + + if (el == null) + throw ex; + + 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 getFiltered( + Class clazz, GetReflectiveElement jlrGetter, + Consumer setAccessible, Predicate elementChecker, + String privateMethodName, String copyMethodName) throws E { + Objects.requireNonNull(clazz, "Class instance not provided"); + + T el = null; + + E ex = null; + try { + el = jlrGetter.get(clazz); + } catch (ReflectiveOperationException e) { + @SuppressWarnings("unchecked") + E ee = (E) e; + ex = ee; + } + + try { + @SuppressWarnings("unchecked") + T[] elements = (T[]) ofClassOfInstance(clazz).method(privateMethodName, boolean.class).invoke(clazz, false); + for (T element : elements) { + if (elementChecker.test(element)) { + // the values in the elements array have to be copied + // (using special private methods in reflection api) before using it + Object reflectionFactoryOfClazz = ofClassOfInstance(clazz).method("getReflectionFactory").invoke(clazz); + @SuppressWarnings("unchecked") + T copiedElement = (T) ofClassOfInstance(reflectionFactoryOfClazz) + .method(copyMethodName, element.getClass()) + .invoke(reflectionFactoryOfClazz, element); + el = copiedElement; + break; + } + } + } catch (ReflectiveOperationException e) { + if (ex != null) + ex.addSuppressed(e); + } + + + if (el == null) + throw ex; + + setAccessible.accept(el); + + return el; + } + + + + + + + + + + + + + + private static Cache, List>> subClassesLists = CacheBuilder.newBuilder() + .expireAfterAccess(10, TimeUnit.MINUTES) + .build(); + + public static List> getAllSubclasses(Class clazz) { + try { + @SuppressWarnings("unchecked") + List> classes = (List>) (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; + } + + } + + + + +} diff --git a/Core/src/main/java/fr/pandacube/lib/core/util/ReflectionUtil.java b/Core/src/main/java/fr/pandacube/lib/core/util/ReflectionUtil.java deleted file mode 100644 index 63128ea..0000000 --- a/Core/src/main/java/fr/pandacube/lib/core/util/ReflectionUtil.java +++ /dev/null @@ -1,379 +0,0 @@ -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 record MethodCacheKey(Class clazz, String methodName, List> parameters) { } - private static final Map methodCache; - - private record FieldCacheKey(Class clazz, String fieldName) { }; - private static final Map fieldCache; - - private static final Unsafe sunMiscUnsafeInstance; - - static { - methodCache = new HashMap<>(); - fieldCache = new HashMap<>(); - - try { - sunMiscUnsafeInstance = (Unsafe) field("theUnsafe") - .inClass(Unsafe.class) - .getStaticValue(); - } catch (Exception e) { - throw new RuntimeException("Cannot access to " + Unsafe.class + ".theUnsafe value.", e); - } - } - - - - - - - - - - - - - - - - - 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) doesn’t 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> 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> 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 { - public T get(Class clazz) throws E; - } - - private static T getDeclaredRecursively( - Class clazz, String name, GetDeclaredFunction func, - Map cache, Function, K> cacheKeySupplier, Consumer setAccessible) throws E { - Objects.requireNonNull(clazz, "Class instance not provided"); - Objects.requireNonNull(name, "Field name not provided"); - - E ex = null; - List> 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 getFiltered( - Class clazz, String name, GetDeclaredFunction func, - Map cache, Function, K> cacheKeySupplier, - Consumer setAccessible, Function 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, List>> subClassesLists = CacheBuilder.newBuilder() - .expireAfterAccess(10, TimeUnit.MINUTES) - .build(); - - public static List> getAllSubclasses(Class clazz) { - try { - @SuppressWarnings("unchecked") - List> classes = (List>) (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; - } - - } - - - - -} diff --git a/Paper/src/main/java/fr/pandacube/lib/paper/util/BukkitEvent.java b/Paper/src/main/java/fr/pandacube/lib/paper/util/BukkitEvent.java index 1f4edf9..a4fa3fe 100644 --- a/Paper/src/main/java/fr/pandacube/lib/paper/util/BukkitEvent.java +++ b/Paper/src/main/java/fr/pandacube/lib/paper/util/BukkitEvent.java @@ -15,7 +15,7 @@ import org.bukkit.plugin.IllegalPluginAccessException; import org.bukkit.plugin.RegisteredListener; import org.bukkit.scheduler.BukkitTask; -import fr.pandacube.lib.core.util.ReflectionUtil; +import fr.pandacube.lib.core.util.Reflect; import fr.pandacube.lib.paper.PandaLibPaper; public class BukkitEvent { @@ -50,7 +50,7 @@ public class BukkitEvent { public static List> getAllEventClasses() { - List> classes = ReflectionUtil.getAllSubclasses(Event.class); + List> classes = Reflect.getAllSubclasses(Event.class); classes.removeIf(e -> getHandlerList(e) == null); return classes; }