From a82820cc154157cd83289749cc73d9d4f41bb41a Mon Sep 17 00:00:00 2001 From: Marc Baloup Date: Mon, 27 Dec 2021 17:53:17 +0100 Subject: [PATCH] Refactor ReflectionUtil (renamed class + changed a lot of stuff inside) and added support for accessing filtered field and changing final fields --- .../lib/bungee/commands/BrigadierCommand.java | 4 +- .../pandacube/lib/cli/BrigadierCommand.java | 4 +- .../lib/core/util/ReflectionUtil.java | 376 ++++++++++++++++++ .../lib/core/util/ReflexionUtil.java | 235 ----------- .../pandacube/lib/paper/util/BukkitEvent.java | 4 +- 5 files changed, 382 insertions(+), 241 deletions(-) create mode 100644 Core/src/main/java/fr/pandacube/lib/core/util/ReflectionUtil.java delete mode 100644 Core/src/main/java/fr/pandacube/lib/core/util/ReflexionUtil.java 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 cdc0fe3..5cf8660 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.ReflexionUtil; +import fr.pandacube.lib.core.util.ReflectionUtil; 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) ReflexionUtil.getDeclaredFieldValue(SuggestionsBuilder.class, builder, "result"); + return (List) ReflectionUtil.field("result").inClass(SuggestionsBuilder.class).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 72a24be..87bd047 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.ReflexionUtil; +import fr.pandacube.lib.core.util.ReflectionUtil; 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) ReflexionUtil.getDeclaredFieldValue(SuggestionsBuilder.class, builder, "result"); + return (List) ReflectionUtil.field("result").inClass(SuggestionsBuilder.class).getValue(builder); } catch (ReflectiveOperationException e) { throw new RuntimeException(e); } 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 new file mode 100644 index 0000000..78868ef --- /dev/null +++ b/Core/src/main/java/fr/pandacube/lib/core/util/ReflectionUtil.java @@ -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> parameters) { } + private static final Map methodCache = new HashMap<>(); + + private record FieldCacheKey(Class clazz, String fieldName) { }; + private static final Map 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) 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/Core/src/main/java/fr/pandacube/lib/core/util/ReflexionUtil.java b/Core/src/main/java/fr/pandacube/lib/core/util/ReflexionUtil.java deleted file mode 100644 index 2f134bf..0000000 --- a/Core/src/main/java/fr/pandacube/lib/core/util/ReflexionUtil.java +++ /dev/null @@ -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, 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 b99e233..1f4edf9 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.ReflexionUtil; +import fr.pandacube.lib.core.util.ReflectionUtil; import fr.pandacube.lib.paper.PandaLibPaper; public class BukkitEvent { @@ -50,7 +50,7 @@ public class BukkitEvent { public static List> getAllEventClasses() { - List> classes = ReflexionUtil.getAllSubclasses(Event.class); + List> classes = ReflectionUtil.getAllSubclasses(Event.class); classes.removeIf(e -> getHandlerList(e) == null); return classes; }