From 2469e124bdb503fcc52bf549fcba40a66d1ceb54 Mon Sep 17 00:00:00 2001 From: JeremyStarTM Date: Sun, 21 Jul 2024 21:10:49 +0200 Subject: [PATCH] Improve event system and reflection --- .../base/classes/helpers/EventHelper.java | 48 +++++++++++----- .../sosengine/base/events/LogEvent.java | 20 +------ .../base/events/ThrowableCatchEvent.java | 19 +------ ...tanceMethodFromStaticContextException.java | 39 +++++++++++++ .../reflection/InvalidMethodSignature.java | 28 ++++++++++ .../StaticInitializerException.java | 56 +++++++++++++++++++ .../sosengine/base/logging/Logger.java | 3 +- .../base/reflection/ReflectionMethod.java | 32 ++++++++--- .../events/GraphicsApiErrorEvent.java | 16 +----- .../graphics/events/GraphicsErrorEvent.java | 11 +--- 10 files changed, 188 insertions(+), 84 deletions(-) create mode 100644 base/src/main/java/de/staropensource/sosengine/base/exceptions/reflection/InstanceMethodFromStaticContextException.java create mode 100644 base/src/main/java/de/staropensource/sosengine/base/exceptions/reflection/InvalidMethodSignature.java create mode 100644 base/src/main/java/de/staropensource/sosengine/base/exceptions/reflection/StaticInitializerException.java diff --git a/base/src/main/java/de/staropensource/sosengine/base/classes/helpers/EventHelper.java b/base/src/main/java/de/staropensource/sosengine/base/classes/helpers/EventHelper.java index 49f0c557..4d8ad199 100644 --- a/base/src/main/java/de/staropensource/sosengine/base/classes/helpers/EventHelper.java +++ b/base/src/main/java/de/staropensource/sosengine/base/classes/helpers/EventHelper.java @@ -22,7 +22,16 @@ package de.staropensource.sosengine.base.classes.helpers; import de.staropensource.sosengine.base.EngineConfiguration; import de.staropensource.sosengine.base.annotations.EventListener; import de.staropensource.sosengine.base.classes.Event; +import de.staropensource.sosengine.base.exceptions.UnexpectedThrowableException; +import de.staropensource.sosengine.base.exceptions.reflection.InstanceMethodFromStaticContextException; +import de.staropensource.sosengine.base.exceptions.reflection.InvalidMethodSignature; +import de.staropensource.sosengine.base.exceptions.reflection.NoAccessException; +import de.staropensource.sosengine.base.exceptions.reflection.StaticInitializerException; import de.staropensource.sosengine.base.logging.Logger; +import de.staropensource.sosengine.base.logging.LoggerInstance; +import de.staropensource.sosengine.base.reflection.Reflect; +import de.staropensource.sosengine.base.reflection.ReflectionMethod; +import de.staropensource.sosengine.base.types.CodePart; import de.staropensource.sosengine.base.types.logging.LogIssuer; import de.staropensource.sosengine.base.utility.ListFormatter; import lombok.Getter; @@ -53,7 +62,7 @@ public class EventHelper { * * @since v1-alpha0 */ - private static final HashMap<@NotNull Class, LinkedList<@NotNull Method>> cachedEventListeners = new HashMap<>(); + private static final HashMap<@NotNull Class, LinkedList<@NotNull ReflectionMethod>> cachedEventListeners = new HashMap<>(); /** * Constructs this class. @@ -85,8 +94,8 @@ public class EventHelper { * @since v1-alpha0 */ @NotNull - public static LinkedList getAnnotatedMethods(@NotNull Class clazz, boolean forceScanning) { - LinkedList methods = new LinkedList<>(); + public static LinkedList getAnnotatedMethods(@NotNull Class clazz, boolean forceScanning) { + LinkedList methods = new LinkedList<>(); if (forceScanning || !cachedEventListeners.containsKey(clazz)) { // Scan entire classpath through Reflections library @@ -103,10 +112,10 @@ public class EventHelper { // Sort event listeners not listening for this event out for (Method method : annotatedMethods) if (method.getAnnotation(EventListener.class).event() == clazz) - methods.add(method); + methods.add(Reflect.reflectOn(method)); // Sort 'methods' linked list after event priority - methods.sort(Comparator.comparing(method0 -> method0.getAnnotation(EventListener.class).priority())); + methods.sort(Comparator.comparing(method -> method.getAnnotation(EventListener.class).priority())); } else // 'forcedScanning' is false and matching event listeners are cached methods = cachedEventListeners.get(clazz); @@ -122,25 +131,36 @@ public class EventHelper { * @since v1-alpha0 */ @NotNull - public static LinkedList getAnnotatedMethods(@NotNull Class clazz) { + public static LinkedList getAnnotatedMethods(@NotNull Class clazz) { return getAnnotatedMethods(clazz, false); } /** * Invokes all matching event listeners without any arguments. * - * @param clazz event class + * @param event event class * @since v1-alpha0 */ - public static void invokeAnnotatedMethods(@NotNull Class clazz) { + public static void invokeAnnotatedMethods(@NotNull Class event, Object... arguments) { Runnable eventCode = () -> { - logCall(clazz); + LoggerInstance logger = new LoggerInstance(new LogIssuer(EventHelper.class, event.getName(), CodePart.ENGINE)); + logCall(event); - for (Method method : getAnnotatedMethods(clazz)) { + for (ReflectionMethod method : getAnnotatedMethods(event)) { try { - method.invoke(null); - } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | - NullPointerException | ExceptionInInitializerError ignored) { + method.invoke(arguments); + } catch (UnexpectedThrowableException exception) { + logger.crash("Event listener method " + method.getName() + " could not be called as an error occured during reflection", exception, true); + } catch (NoAccessException exception) { + logger.warn("Event listener method " + method.getName() + " could not be called as the method could not be accessed"); + } catch (InvalidMethodSignature exception) { + logger.warn("Event listener method " + method.getName() + " has an invalid method signature"); + } catch (InvocationTargetException exception) { + logger.crash("Event listener method " + method.getName() + " threw a throwable", exception.getTargetException(), true); + } catch (InstanceMethodFromStaticContextException exception) { + logger.warn("Event listener method " + method.getName() + " is not static. Event listener methods must be static for them to be called."); + } catch (StaticInitializerException exception) { + logger.crash("Event listener method " + method.getName() + " could not be called as the static initializer failed", exception.getThrowable(), true); } } }; @@ -163,7 +183,7 @@ public class EventHelper { for (Class event : cachedEventListeners.keySet()) precomputeEventListeners(event); else { - LinkedList<@NotNull Method> annotatedMethods = getAnnotatedMethods(clazz); + LinkedList<@NotNull ReflectionMethod> annotatedMethods = getAnnotatedMethods(clazz); if (cachedEventListeners.containsKey(clazz)) cachedEventListeners.replace(clazz, annotatedMethods); diff --git a/base/src/main/java/de/staropensource/sosengine/base/events/LogEvent.java b/base/src/main/java/de/staropensource/sosengine/base/events/LogEvent.java index d6233cab..f5617960 100644 --- a/base/src/main/java/de/staropensource/sosengine/base/events/LogEvent.java +++ b/base/src/main/java/de/staropensource/sosengine/base/events/LogEvent.java @@ -19,16 +19,12 @@ package de.staropensource.sosengine.base.events; -import de.staropensource.sosengine.base.EngineConfiguration; import de.staropensource.sosengine.base.classes.Event; import de.staropensource.sosengine.base.classes.helpers.EventHelper; import de.staropensource.sosengine.base.types.logging.LogIssuer; import de.staropensource.sosengine.base.types.logging.LogLevel; import org.jetbrains.annotations.NotNull; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - /** * Called before a new log message is printed. * @@ -59,20 +55,6 @@ public final class LogEvent implements Event { * @since v1-alpha0 */ public void callEvent(@NotNull LogLevel level, @NotNull LogIssuer logIssuer, @NotNull String message) { - Runnable eventCode = Thread.ofVirtual().start(() -> { - // Uncommenting this would be a great way to cause a StackOverflowException! - //EventHelper.logCall(getClass(), level, logIssuer, message); - - for (Method method : EventHelper.getAnnotatedMethods(getClass())) { - try { - method.invoke(null, level, logIssuer, message); - } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | NullPointerException | ExceptionInInitializerError ignored) {} - } - }); - - if (EngineConfiguration.getInstance().isOptimizeEvents()) - Thread.ofVirtual().start(eventCode); - else - eventCode.run(); + EventHelper.invokeAnnotatedMethods(getClass(), level, logIssuer, message); } } diff --git a/base/src/main/java/de/staropensource/sosengine/base/events/ThrowableCatchEvent.java b/base/src/main/java/de/staropensource/sosengine/base/events/ThrowableCatchEvent.java index fa30afb7..4fdba0ee 100644 --- a/base/src/main/java/de/staropensource/sosengine/base/events/ThrowableCatchEvent.java +++ b/base/src/main/java/de/staropensource/sosengine/base/events/ThrowableCatchEvent.java @@ -19,15 +19,11 @@ package de.staropensource.sosengine.base.events; -import de.staropensource.sosengine.base.EngineConfiguration; import de.staropensource.sosengine.base.classes.Event; import de.staropensource.sosengine.base.classes.helpers.EventHelper; import de.staropensource.sosengine.base.utility.Miscellaneous; import org.jetbrains.annotations.NotNull; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - /** * Called when an exception is caught. * @@ -57,19 +53,6 @@ public final class ThrowableCatchEvent implements Event { * @since v1-alpha0 */ public void callEvent(@NotNull Throwable throwable, @NotNull String identifier) { - Runnable eventCode = Thread.ofVirtual().start(() -> { - EventHelper.logCall(getClass(), throwable, identifier); - - for (Method method : EventHelper.getAnnotatedMethods(getClass())) { - try { - method.invoke(null, throwable, identifier); - } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | NullPointerException | ExceptionInInitializerError ignored) {} - } - }); - - if (EngineConfiguration.getInstance().isOptimizeEvents()) - Thread.ofVirtual().start(eventCode); - else - eventCode.run(); + EventHelper.invokeAnnotatedMethods(getClass(), throwable, identifier); } } diff --git a/base/src/main/java/de/staropensource/sosengine/base/exceptions/reflection/InstanceMethodFromStaticContextException.java b/base/src/main/java/de/staropensource/sosengine/base/exceptions/reflection/InstanceMethodFromStaticContextException.java new file mode 100644 index 00000000..ef88dc0b --- /dev/null +++ b/base/src/main/java/de/staropensource/sosengine/base/exceptions/reflection/InstanceMethodFromStaticContextException.java @@ -0,0 +1,39 @@ +/* + * STAROPENSOURCE ENGINE SOURCE FILE + * Copyright (c) 2024 The StarOpenSource Engine Contributors + * Licensed under the GNU Affero General Public License v3 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.staropensource.sosengine.base.exceptions.reflection; + +import org.jetbrains.annotations.NotNull; + +/** + * Thrown when trying to call a non-static method from a static context. + * + * @since v1-alpha2 + */ +public class InstanceMethodFromStaticContextException extends Exception { + /** + * Constructs this exception. + * + * @param methodName name of the method + * @since v1-alpha2 + */ + public InstanceMethodFromStaticContextException(@NotNull String methodName) { + super("Method " + methodName + " could not be called as the method is non-static and was called from a static context"); + } +} diff --git a/base/src/main/java/de/staropensource/sosengine/base/exceptions/reflection/InvalidMethodSignature.java b/base/src/main/java/de/staropensource/sosengine/base/exceptions/reflection/InvalidMethodSignature.java new file mode 100644 index 00000000..9c7fa2a1 --- /dev/null +++ b/base/src/main/java/de/staropensource/sosengine/base/exceptions/reflection/InvalidMethodSignature.java @@ -0,0 +1,28 @@ +/* + * STAROPENSOURCE ENGINE SOURCE FILE + * Copyright (c) 2024 The StarOpenSource Engine Contributors + * Licensed under the GNU Affero General Public License v3 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.staropensource.sosengine.base.exceptions.reflection; + +import org.jetbrains.annotations.NotNull; + +public class InvalidMethodSignature extends Exception { + public InvalidMethodSignature(@NotNull String methodName) { + super("Method " + methodName + " has a different method signature"); + } +} diff --git a/base/src/main/java/de/staropensource/sosengine/base/exceptions/reflection/StaticInitializerException.java b/base/src/main/java/de/staropensource/sosengine/base/exceptions/reflection/StaticInitializerException.java new file mode 100644 index 00000000..e9a7422b --- /dev/null +++ b/base/src/main/java/de/staropensource/sosengine/base/exceptions/reflection/StaticInitializerException.java @@ -0,0 +1,56 @@ +/* + * STAROPENSOURCE ENGINE SOURCE FILE + * Copyright (c) 2024 The StarOpenSource Engine Contributors + * Licensed under the GNU Affero General Public License v3 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.staropensource.sosengine.base.exceptions.reflection; + +import lombok.Getter; +import org.jetbrains.annotations.NotNull; + +/** + * Thrown when an exception occurs + * while executing a static initializer. + * + * @since v1-alpha2 + */ +@Getter +@SuppressWarnings({ "unused", "JavadocDeclaration", "JavadocBlankLines" }) +public class StaticInitializerException extends Exception { + /** + * Contains the throwable supplied to the constructor. + * + * @since v1-alpha2 + * + * -- GETTER -- + * Returns the throwable supplied to the constructor. + * + * @return throwable thrown by the static initializer + * @since v1-alpha2 + */ + private final Throwable throwable; + + /** + * Constructs this exception. + * + * @param throwable throwable thrown by the static initializer + * @since v1-alpha2 + */ + public StaticInitializerException(@NotNull Throwable throwable) { + this.throwable = throwable; + } +} diff --git a/base/src/main/java/de/staropensource/sosengine/base/logging/Logger.java b/base/src/main/java/de/staropensource/sosengine/base/logging/Logger.java index c7e26908..0094dfa8 100644 --- a/base/src/main/java/de/staropensource/sosengine/base/logging/Logger.java +++ b/base/src/main/java/de/staropensource/sosengine/base/logging/Logger.java @@ -23,6 +23,7 @@ import de.staropensource.sosengine.base.Engine; import de.staropensource.sosengine.base.EngineConfiguration; import de.staropensource.sosengine.base.classes.LoggerImpl; import de.staropensource.sosengine.base.classes.Placeholder; +import de.staropensource.sosengine.base.classes.helpers.EventHelper; import de.staropensource.sosengine.base.events.LogEvent; import de.staropensource.sosengine.base.internal.placeholders.logger.*; import de.staropensource.sosengine.base.internal.types.QueuedLogMessage; @@ -214,7 +215,7 @@ public final class Logger { base = loggerImplementation.postPlaceholder(level, issuer, base); // Call event - if (!issuer.getClazz().getName().equals("de.staropensource.sosengine.slf4j_compat.CompatibilityLogger")) + if (!(issuer.getClazz().getName().equals("de.staropensource.sosengine.slf4j_compat.CompatibilityLogger") || issuer.getClazz().equals(EventHelper.class))) new LogEvent().callEvent(level, issuer, message); // Print log message by invoking LoggerImpl#print diff --git a/base/src/main/java/de/staropensource/sosengine/base/reflection/ReflectionMethod.java b/base/src/main/java/de/staropensource/sosengine/base/reflection/ReflectionMethod.java index 83ac2ea9..657f963f 100644 --- a/base/src/main/java/de/staropensource/sosengine/base/reflection/ReflectionMethod.java +++ b/base/src/main/java/de/staropensource/sosengine/base/reflection/ReflectionMethod.java @@ -21,7 +21,10 @@ package de.staropensource.sosengine.base.reflection; import de.staropensource.sosengine.base.exceptions.UnexpectedCheckEndException; import de.staropensource.sosengine.base.exceptions.UnexpectedThrowableException; +import de.staropensource.sosengine.base.exceptions.reflection.InstanceMethodFromStaticContextException; +import de.staropensource.sosengine.base.exceptions.reflection.InvalidMethodSignature; import de.staropensource.sosengine.base.exceptions.reflection.NoAccessException; +import de.staropensource.sosengine.base.exceptions.reflection.StaticInitializerException; import de.staropensource.sosengine.base.internal.reflection.ReflectionAccessWidener; import de.staropensource.sosengine.base.types.reflection.VisibilityModifier; import lombok.Getter; @@ -310,11 +313,12 @@ public final class ReflectionMethod { /** * Returns the specified annotation or {@code null} if not found. * - * @param annotation class + * @param annotation class + * @param annotation annotation class * @return annotation or {@code null} * @since v1-alpha2 */ - public Annotation getAnnotation(@NotNull Class annotation) { + public T getAnnotation(@NotNull Class annotation) { return method.getAnnotation(annotation); } @@ -345,13 +349,16 @@ public final class ReflectionMethod { * Invokes the method. * * @return method return value - * @throws NoAccessException if access to the method has been denied - * @throws InvocationTargetException covers exceptions thrown by the method * @throws UnexpectedThrowableException if the {@code modifiers} field could not be found + * @throws NoAccessException if access to the method has been denied + * @throws InvalidMethodSignature if the method signature is incorrect + * @throws InvocationTargetException covers exceptions thrown by the method + * @throws InstanceMethodFromStaticContextException when the target method is non-static and called from a static context + * @throws StaticInitializerException when an the static initializer fails * @since v1-alpha2 */ @Nullable - public Object invoke() throws NoAccessException, InvocationTargetException, UnexpectedThrowableException { + public Object invoke() throws UnexpectedThrowableException, NoAccessException, InvalidMethodSignature, InvocationTargetException, InstanceMethodFromStaticContextException, StaticInitializerException { return invoke(new Object[0]); } @@ -359,13 +366,16 @@ public final class ReflectionMethod { * Invokes the method. * * @return method return value - * @throws NoAccessException if access to the method has been denied - * @throws InvocationTargetException covers exceptions thrown by the method * @throws UnexpectedThrowableException if the {@code modifiers} field could not be found + * @throws NoAccessException if access to the method has been denied + * @throws InvalidMethodSignature if the method signature is incorrect + * @throws InvocationTargetException covers exceptions thrown by the method + * @throws InstanceMethodFromStaticContextException when the target method is non-static and called from a static context + * @throws StaticInitializerException when an the static initializer fails * @since v1-alpha2 */ @Nullable - public Object invoke(Object... args) throws NoAccessException, InvocationTargetException, UnexpectedThrowableException { + public Object invoke(Object... args) throws UnexpectedThrowableException, NoAccessException, InvalidMethodSignature, InvocationTargetException, InstanceMethodFromStaticContextException, StaticInitializerException { Object returnValue; // Allow access to method @@ -377,6 +387,12 @@ public final class ReflectionMethod { } catch (IllegalAccessException exception) { //ReflectionAccessWidener.lockModifications(this, updatedModifiers); // Lock method before throwing exception throw new NoAccessException("method", getName()); + } catch (IllegalArgumentException exception) { + throw new InvalidMethodSignature(getName()); + } catch (NullPointerException exception) { + throw new InstanceMethodFromStaticContextException(getName()); + } catch (ExceptionInInitializerError exception) { + throw new StaticInitializerException(exception.getCause()); } // Return return value from method diff --git a/graphics/src/main/java/de/staropensource/sosengine/graphics/events/GraphicsApiErrorEvent.java b/graphics/src/main/java/de/staropensource/sosengine/graphics/events/GraphicsApiErrorEvent.java index 3a7c650b..3166748f 100644 --- a/graphics/src/main/java/de/staropensource/sosengine/graphics/events/GraphicsApiErrorEvent.java +++ b/graphics/src/main/java/de/staropensource/sosengine/graphics/events/GraphicsApiErrorEvent.java @@ -21,14 +21,8 @@ package de.staropensource.sosengine.graphics.events; import de.staropensource.sosengine.base.classes.Event; import de.staropensource.sosengine.base.classes.helpers.EventHelper; -import de.staropensource.sosengine.base.events.LogEvent; -import de.staropensource.sosengine.base.types.logging.LogIssuer; -import de.staropensource.sosengine.base.types.logging.LogLevel; import org.jetbrains.annotations.NotNull; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - /** * Called when a graphics error occurs. * @@ -44,7 +38,7 @@ public class GraphicsApiErrorEvent implements Event { /** * {@inheritDoc} * @deprecated use the {@code callEvent} method with arguments - * @see LogEvent#callEvent(LogLevel, LogIssuer, String) + * @see #callEvent(String) */ @Deprecated @Override @@ -57,12 +51,6 @@ public class GraphicsApiErrorEvent implements Event { * @since v1-alpha0 */ public void callEvent(@NotNull String error) { - EventHelper.logCall(getClass()); - - for (Method method : EventHelper.getAnnotatedMethods(getClass())) { - try { - method.invoke(null, error); - } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | NullPointerException | ExceptionInInitializerError ignored) {} - } + EventHelper.invokeAnnotatedMethods(getClass(), error); } } diff --git a/graphics/src/main/java/de/staropensource/sosengine/graphics/events/GraphicsErrorEvent.java b/graphics/src/main/java/de/staropensource/sosengine/graphics/events/GraphicsErrorEvent.java index c4654716..ce642a35 100644 --- a/graphics/src/main/java/de/staropensource/sosengine/graphics/events/GraphicsErrorEvent.java +++ b/graphics/src/main/java/de/staropensource/sosengine/graphics/events/GraphicsErrorEvent.java @@ -26,9 +26,6 @@ import de.staropensource.sosengine.base.types.logging.LogIssuer; import de.staropensource.sosengine.base.types.logging.LogLevel; import org.jetbrains.annotations.NotNull; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - /** * Called when a graphics error occurs. * @@ -57,12 +54,6 @@ public class GraphicsErrorEvent implements Event { * @since v1-alpha0 */ public void callEvent(@NotNull String error) { - EventHelper.logCall(getClass()); - - for (Method method : EventHelper.getAnnotatedMethods(getClass())) { - try { - method.invoke(null, error); - } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | NullPointerException | ExceptionInInitializerError ignored) {} - } + EventHelper.invokeAnnotatedMethods(getClass(), error); } }