Improve event system and reflection

This commit is contained in:
JeremyStar™ 2024-07-21 21:10:49 +02:00
parent 350b18219b
commit 2469e124bd
Signed by: JeremyStarTM
GPG key ID: E366BAEF67E4704D
10 changed files with 188 additions and 84 deletions

View file

@ -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<? extends Event>, LinkedList<@NotNull Method>> cachedEventListeners = new HashMap<>();
private static final HashMap<@NotNull Class<? extends Event>, LinkedList<@NotNull ReflectionMethod>> cachedEventListeners = new HashMap<>();
/**
* Constructs this class.
@ -85,8 +94,8 @@ public class EventHelper {
* @since v1-alpha0
*/
@NotNull
public static LinkedList<Method> getAnnotatedMethods(@NotNull Class<? extends Event> clazz, boolean forceScanning) {
LinkedList<Method> methods = new LinkedList<>();
public static LinkedList<ReflectionMethod> getAnnotatedMethods(@NotNull Class<? extends Event> clazz, boolean forceScanning) {
LinkedList<ReflectionMethod> 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<Method> getAnnotatedMethods(@NotNull Class<? extends Event> clazz) {
public static LinkedList<ReflectionMethod> getAnnotatedMethods(@NotNull Class<? extends Event> 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<? extends Event> clazz) {
public static void invokeAnnotatedMethods(@NotNull Class<? extends Event> 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<? extends Event> 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);

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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");
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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");
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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;
}
}

View file

@ -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

View file

@ -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 <T> annotation class
* @param annotation annotation class
* @return annotation or {@code null}
* @since v1-alpha2
*/
public Annotation getAnnotation(@NotNull Class<Annotation> annotation) {
public <T extends Annotation> T getAnnotation(@NotNull Class<T> 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

View file

@ -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);
}
}

View file

@ -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);
}
}