Add support for disabling classpath scanning
This commit however does not implement support for Substrate VM/native-image, which I've already tested. Sad.
This commit is contained in:
parent
ebbc1778ae
commit
0fbfe8f4e3
10 changed files with 553 additions and 119 deletions
|
@ -20,15 +20,13 @@
|
|||
package de.staropensource.sosengine.base;
|
||||
|
||||
import de.staropensource.sosengine.base.annotation.EngineSubsystem;
|
||||
import de.staropensource.sosengine.base.implementable.ShutdownHandler;
|
||||
import de.staropensource.sosengine.base.implementable.SubsystemClass;
|
||||
import de.staropensource.sosengine.base.implementable.helper.EventHelper;
|
||||
import de.staropensource.sosengine.base.utility.information.EngineInformation;
|
||||
import de.staropensource.sosengine.base.utility.information.JvmInformation;
|
||||
import de.staropensource.sosengine.base.implementation.versioning.StarOpenSourceVersioningSystem;
|
||||
import de.staropensource.sosengine.base.event.*;
|
||||
import de.staropensource.sosengine.base.exception.IllegalAccessException;
|
||||
import de.staropensource.sosengine.base.exception.dependency.UnmetDependenciesException;
|
||||
import de.staropensource.sosengine.base.implementable.ShutdownHandler;
|
||||
import de.staropensource.sosengine.base.implementable.SubsystemClass;
|
||||
import de.staropensource.sosengine.base.implementable.helper.EventHelper;
|
||||
import de.staropensource.sosengine.base.implementation.versioning.StarOpenSourceVersioningSystem;
|
||||
import de.staropensource.sosengine.base.internal.event.InternalEngineShutdownEvent;
|
||||
import de.staropensource.sosengine.base.internal.type.DependencySubsystemVector;
|
||||
import de.staropensource.sosengine.base.logging.*;
|
||||
|
@ -38,6 +36,8 @@ import de.staropensource.sosengine.base.type.immutable.ImmutableLinkedList;
|
|||
import de.staropensource.sosengine.base.utility.DependencyResolver;
|
||||
import de.staropensource.sosengine.base.utility.Miscellaneous;
|
||||
import de.staropensource.sosengine.base.utility.PlaceholderEngine;
|
||||
import de.staropensource.sosengine.base.utility.information.EngineInformation;
|
||||
import de.staropensource.sosengine.base.utility.information.JvmInformation;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
@ -249,12 +249,34 @@ public final class Engine extends SubsystemClass {
|
|||
private boolean checkEnvironment() {
|
||||
logger.diag("Checking environment");
|
||||
|
||||
// Warn about potential Java incompatibilities
|
||||
if (JvmInformation.getJavaVersion() > EngineInformation.getJavaSource())
|
||||
logger.warn("The StarOpenSource Engine is running on an untested Java version.\nThings may not work as expected or features which can improve performance, stability, compatibility or ease of use may be missing.\nIf you encounter issues, try running a JVM implementing Java " + EngineInformation.getJavaSource());
|
||||
|
||||
|
||||
// Check if reflective classpath scanning is supported
|
||||
if (checkClasspathScanningSupport()) {
|
||||
logger.warn("Running in an classpath scanning-unfriendly environment, disabling.");
|
||||
logger.warn("If shit doesn't work and is expected to be discovered by annotations, you'll need to");
|
||||
logger.warn("either register it first or have to place classes in some package.");
|
||||
logger.warn("Please consult sos!engine's documentation for more information about this issue.");
|
||||
EngineInternals.getInstance().overrideReflectiveClasspathScanning(false);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether scanning the classpath is supported.
|
||||
*
|
||||
* @return test results
|
||||
* @since v1-alpha5
|
||||
*/
|
||||
private boolean checkClasspathScanningSupport() {
|
||||
// This may be expanded in the future
|
||||
return EngineConfiguration.getInstance().isInitialForceDisableClasspathScanning();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the execution safety of the environment.
|
||||
*
|
||||
|
@ -354,21 +376,11 @@ public final class Engine extends SubsystemClass {
|
|||
*/
|
||||
private void collectSubsystems() {
|
||||
ArrayList<@NotNull DependencySubsystemVector> subsystemsMutable = new ArrayList<>();
|
||||
|
||||
// Scan entire classpath using the Reflections library
|
||||
Reflections reflections = new Reflections(
|
||||
new ConfigurationBuilder()
|
||||
.setUrls(ClasspathHelper.forJavaClassPath())
|
||||
.setScanners(Scanners.TypesAnnotated)
|
||||
);
|
||||
|
||||
// Get annotated methods
|
||||
Set<@NotNull Class<?>> annotatedClasses = reflections.getTypesAnnotatedWith(EngineSubsystem.class);
|
||||
|
||||
// Initialize classes, get dependency vector and add to 'subsystemsMutable'
|
||||
Object initializedClassRaw;
|
||||
SubsystemClass initializedClass;
|
||||
for (Class<?> clazz : annotatedClasses) {
|
||||
|
||||
// Check and initialize all classes, get dependency vector and check version, then add to 'subsystemsMutable'
|
||||
for (Class<?> clazz : getRawSubsystemClasses())
|
||||
try {
|
||||
// Create new instance
|
||||
initializedClassRaw = clazz.getDeclaredConstructor().newInstance();
|
||||
|
@ -387,12 +399,43 @@ public final class Engine extends SubsystemClass {
|
|||
logger.crash("Failed to initialize subsystem " + clazz.getName() + ": Invalid version string: " + exception.getMessage().replace("The version string is invalid: ", ""));
|
||||
logger.crash("Failed to initialize subsystem " + clazz.getName() + ": Method invocation error", exception);
|
||||
}
|
||||
}
|
||||
|
||||
// Update 'subsystems'
|
||||
subsystems = new ImmutableLinkedList<>(subsystemsMutable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of classes which are potentially
|
||||
* eligible for subsystem initialization.
|
||||
*
|
||||
* @return potential subsystem classes
|
||||
* @since v1-alpha5
|
||||
*/
|
||||
private Set<@NotNull Class<?>> getRawSubsystemClasses() {
|
||||
Set<@NotNull Class<?>> classes = new HashSet<>();
|
||||
|
||||
if (EngineInternals.getInstance().getReflectiveClasspathScanning()) {
|
||||
// Scan entire classpath using the Reflections library
|
||||
Reflections reflections = new Reflections(
|
||||
new ConfigurationBuilder()
|
||||
.setUrls(ClasspathHelper.forJavaClassPath())
|
||||
.setScanners(Scanners.TypesAnnotated)
|
||||
);
|
||||
|
||||
// Get annotated methods
|
||||
classes = reflections.getTypesAnnotatedWith(EngineSubsystem.class);
|
||||
} else
|
||||
for (String path : EngineConfiguration.getInstance().getInitialIncludeSubsystemClasses())
|
||||
try {
|
||||
logger.diag("Resolving class " + path);
|
||||
classes.add(Class.forName(path));
|
||||
} catch (ClassNotFoundException exception) {
|
||||
logger.error("Failed loading subsystem class " + path + ": Class not found");
|
||||
}
|
||||
|
||||
return classes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes all subsystems.
|
||||
*
|
||||
|
|
|
@ -32,7 +32,10 @@ import lombok.Getter;
|
|||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Provides the base engine configuration.
|
||||
|
@ -119,6 +122,35 @@ public final class EngineConfiguration extends Configuration {
|
|||
*/
|
||||
private boolean debugShortcodeConverter;
|
||||
|
||||
/**
|
||||
* If enabled, will force-disable reflective classpath scanning.
|
||||
*
|
||||
* @see EngineInternals#getReflectiveClasspathScanning()
|
||||
* @see EngineInternals#overrideReflectiveClasspathScanning(boolean)
|
||||
* @since v1-alpha5
|
||||
* -- GETTER --
|
||||
* Gets the value for {@link #initialForceDisableClasspathScanning}.
|
||||
*
|
||||
* @return variable value
|
||||
* @see #initialForceDisableClasspathScanning
|
||||
* @since v1-alpha5
|
||||
*/
|
||||
private boolean initialForceDisableClasspathScanning;
|
||||
|
||||
/**
|
||||
* Will try to load the specified classes as subsystems,
|
||||
* if reflective classpath scanning is disabled.
|
||||
*
|
||||
* @since v1-alpha5
|
||||
* -- GETTER --
|
||||
* Gets the value for {@link #initialIncludeSubsystemClasses}.
|
||||
*
|
||||
* @return variable value
|
||||
* @see #initialIncludeSubsystemClasses
|
||||
* @since v1-alpha5
|
||||
*/
|
||||
private Set<@NotNull String> initialIncludeSubsystemClasses;
|
||||
|
||||
/**
|
||||
* If enabled, invalid shortcodes will be logged by the {@link ShortcodeParser}.
|
||||
* The message will be printed as a {@link LogLevel#SILENT_WARNING}.
|
||||
|
@ -322,6 +354,12 @@ public final class EngineConfiguration extends Configuration {
|
|||
case "debugEvents" -> debugEvents = parser.getBoolean(group + property);
|
||||
case "debugShortcodeConverter" -> debugShortcodeConverter = parser.getBoolean(group + property);
|
||||
|
||||
case "initialForceDisableClasspathScanning" -> initialForceDisableClasspathScanning = parser.getBoolean(group + property);
|
||||
case "initialIncludeSubsystemClasses" -> {
|
||||
initialIncludeSubsystemClasses = new HashSet<>();
|
||||
initialIncludeSubsystemClasses.addAll(Arrays.stream(parser.getString(group + property).split(",")).toList());
|
||||
}
|
||||
|
||||
case "errorShortcodeConverter" -> errorShortcodeConverter = parser.getBoolean(group + property);
|
||||
|
||||
case "optimizeLogging" -> {
|
||||
|
@ -369,6 +407,9 @@ public final class EngineConfiguration extends Configuration {
|
|||
debugEvents = false;
|
||||
debugShortcodeConverter = false;
|
||||
|
||||
initialForceDisableClasspathScanning = false;
|
||||
initialIncludeSubsystemClasses = new HashSet<>();
|
||||
|
||||
errorShortcodeConverter = true;
|
||||
|
||||
optimizeLogging = true;
|
||||
|
@ -393,6 +434,9 @@ public final class EngineConfiguration extends Configuration {
|
|||
case "debugEvents" -> debugEvents;
|
||||
case "debugShortcodeConverter" -> debugShortcodeConverter;
|
||||
|
||||
case "initialForceDisableClasspathScanning" -> initialForceDisableClasspathScanning;
|
||||
case "initialIncludeSubsystemClasses" -> initialIncludeSubsystemClasses;
|
||||
|
||||
case "errorShortcodeConverter" -> errorShortcodeConverter;
|
||||
|
||||
case "optimizeLogging" -> optimizeLogging;
|
||||
|
|
|
@ -19,9 +19,12 @@
|
|||
|
||||
package de.staropensource.sosengine.base;
|
||||
|
||||
import de.staropensource.sosengine.base.implementable.ShutdownHandler;
|
||||
import de.staropensource.sosengine.base.exception.IllegalAccessException;
|
||||
import de.staropensource.sosengine.base.implementable.EventListenerCode;
|
||||
import de.staropensource.sosengine.base.implementable.ShutdownHandler;
|
||||
import de.staropensource.sosengine.base.implementable.helper.EventHelper;
|
||||
import de.staropensource.sosengine.base.logging.LoggerInstance;
|
||||
import de.staropensource.sosengine.base.type.EventPriority;
|
||||
import de.staropensource.sosengine.base.type.InternalAccessArea;
|
||||
import lombok.Getter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
@ -72,6 +75,21 @@ public final class EngineInternals {
|
|||
@Getter
|
||||
private final @NotNull List<@NotNull InternalAccessArea> restrictedAreas = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Contains whether the engine should reflectively
|
||||
* search the classpath for events or other annotations.
|
||||
* <p>
|
||||
* If disabled, code will either have to manually call
|
||||
* registration methods or certain classes have to
|
||||
* be created in a certain package, depending on the
|
||||
* use case and application.
|
||||
*
|
||||
* @see EventHelper#registerEvent(Class, EventListenerCode)
|
||||
* @see EventHelper#registerEvent(Class, EventListenerCode, EventPriority)
|
||||
* @since v1-alpha5
|
||||
*/
|
||||
private boolean reflectiveClasspathScanning = true;
|
||||
|
||||
/**
|
||||
* Constructs this class.
|
||||
*
|
||||
|
@ -110,10 +128,12 @@ public final class EngineInternals {
|
|||
areas.remove(InternalAccessArea.ALL);
|
||||
areas.remove(InternalAccessArea.ALL_READ);
|
||||
areas.remove(InternalAccessArea.ALL_WRITE);
|
||||
areas.remove(InternalAccessArea.ALL_READ_ESSENTIAL);
|
||||
restrictedAreas.addAll(areas);
|
||||
}
|
||||
case ALL_READ -> restrictedAreas.addAll(Arrays.stream(InternalAccessArea.valuesReadOnly()).toList());
|
||||
case ALL_WRITE -> restrictedAreas.addAll(Arrays.stream(InternalAccessArea.valuesWriteOnly()).toList());
|
||||
case ALL_READ -> restrictedAreas.addAll(Arrays.stream(InternalAccessArea.valuesReadOnly()).toList());
|
||||
case ALL_READ_ESSENTIAL -> restrictedAreas.addAll(Arrays.stream(InternalAccessArea.valuesEssentialReadOnly()).toList());
|
||||
default -> restrictedAreas.add(area);
|
||||
}
|
||||
}
|
||||
|
@ -125,11 +145,11 @@ public final class EngineInternals {
|
|||
* Highly recommended to keep enabled.
|
||||
*
|
||||
* @param status {@code true} to install, {@code false} otherwise
|
||||
* @throws IllegalAccessException when restricted
|
||||
* @throws IllegalAccessException when restricted ({@link InternalAccessArea#SAFETY_SHUTDOWN_HOOK_UPDATE})
|
||||
* @since v1-alpha4
|
||||
*/
|
||||
public void installSafetyShutdownHook(boolean status) throws IllegalAccessException {
|
||||
isRestricted(InternalAccessArea.SAFETY_SHUTDOWN_HOOK);
|
||||
isRestricted(InternalAccessArea.SAFETY_SHUTDOWN_HOOK_UPDATE);
|
||||
|
||||
try {
|
||||
if (status)
|
||||
|
@ -139,13 +159,27 @@ public final class EngineInternals {
|
|||
} catch (IllegalArgumentException | IllegalStateException ignored) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the engine's shutdown handler.
|
||||
* The shutdown handler is responsible for
|
||||
* shutting down the JVM safely.
|
||||
*
|
||||
* @return shutdown handler
|
||||
* @throws IllegalAccessException when restricted ({@link InternalAccessArea#SHUTDOWN_HANDLER_GET})
|
||||
* @since v1-alpha4
|
||||
*/
|
||||
public @NotNull ShutdownHandler getShutdownHandler() throws IllegalAccessException {
|
||||
isRestricted(InternalAccessArea.SHUTDOWN_HANDLER_GET);
|
||||
return Engine.getInstance().getShutdownHandler();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the engine's shutdown handler.
|
||||
* The shutdown handler is responsible for
|
||||
* shutting down the JVM safely.
|
||||
*
|
||||
* @param shutdownHandler new shutdown handler
|
||||
* @throws IllegalAccessException when restricted
|
||||
* @throws IllegalAccessException when restricted ({@link InternalAccessArea#SHUTDOWN_HANDLER_UPDATE})
|
||||
* @since v1-alpha4
|
||||
*/
|
||||
public void setShutdownHandler(@NotNull ShutdownHandler shutdownHandler) throws IllegalAccessException {
|
||||
|
@ -154,16 +188,47 @@ public final class EngineInternals {
|
|||
}
|
||||
|
||||
/**
|
||||
* Gets the engine's shutdown handler.
|
||||
* The shutdown handler is responsible for
|
||||
* shutting down the JVM safely.
|
||||
* Returns whether the engine should reflectively
|
||||
* search the classpath for events or other annotations.
|
||||
* <p>
|
||||
* If disabled, code will either have to manually call
|
||||
* registration methods or certain classes have to
|
||||
* be created in a certain package, depending on the
|
||||
* use case and application.
|
||||
*
|
||||
* @return shutdown handler
|
||||
* @throws IllegalAccessException when restricted
|
||||
* @return reflective classpath scanning flag state
|
||||
* @throws IllegalAccessException when restricted ({@link InternalAccessArea#REFLECTIVE_CLASSPATH_SCANNING_GET})
|
||||
* @see EventHelper#registerEvent(Class, EventListenerCode)
|
||||
* @see EventHelper#registerEvent(Class, EventListenerCode, EventPriority)
|
||||
* @since v1-alpha4
|
||||
*/
|
||||
public @NotNull ShutdownHandler getShutdownHandler() throws IllegalAccessException {
|
||||
isRestricted(InternalAccessArea.SHUTDOWN_HANDLER_GET);
|
||||
return Engine.getInstance().getShutdownHandler();
|
||||
public boolean getReflectiveClasspathScanning() throws IllegalAccessException {
|
||||
isRestricted(InternalAccessArea.REFLECTIVE_CLASSPATH_SCANNING_GET);
|
||||
return reflectiveClasspathScanning;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides whether the engine should reflectively
|
||||
* search the classpath for events or other annotations.
|
||||
* <p>
|
||||
* If disabled, code will either have to manually call
|
||||
* registration methods or certain classes have to
|
||||
* be created in a certain package, depending on the
|
||||
* use case and application.
|
||||
* <p>
|
||||
* Enabling reflective classpath scanning in an unsupported
|
||||
* environment may cause minor to extreme side effects,
|
||||
* including but not limited to <b>bugs, exceptions, engine
|
||||
* or even whole JVM crashes</b>. <i>You have been warned!</i>
|
||||
*
|
||||
* @param reflectiveClasspathScanning new reflective classpath scanning
|
||||
* @throws IllegalAccessException when restricted ({@link InternalAccessArea#REFLECTIVE_CLASSPATH_SCANNING_OVERRIDE})
|
||||
* @see EventHelper#registerEvent(Class, EventListenerCode)
|
||||
* @see EventHelper#registerEvent(Class, EventListenerCode, EventPriority)
|
||||
* @since v1-alpha0
|
||||
*/
|
||||
public void overrideReflectiveClasspathScanning(boolean reflectiveClasspathScanning) throws IllegalAccessException {
|
||||
isRestricted(InternalAccessArea.REFLECTIVE_CLASSPATH_SCANNING_OVERRIDE);
|
||||
this.reflectiveClasspathScanning = reflectiveClasspathScanning;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* STAROPENSOURCE ENGINE SOURCE FILE
|
||||
* Copyright (c) 2024 The StarOpenSource Engine Authors
|
||||
* 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.implementable;
|
||||
|
||||
import de.staropensource.sosengine.base.implementable.helper.EventHelper;
|
||||
import de.staropensource.sosengine.base.type.EventPriority;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Used by {@link EventHelper} to execute event listeners.
|
||||
*
|
||||
* @see Runnable
|
||||
* @since v1-alpha5
|
||||
*/
|
||||
public abstract class EventListenerCode {
|
||||
/**
|
||||
* Contains the priority of this
|
||||
* event listener.
|
||||
* <p>
|
||||
* Set automatically by {@link EventHelper},
|
||||
* do not change this manually.
|
||||
*
|
||||
* @since v1-alpha5
|
||||
*/
|
||||
public @NotNull EventPriority priority = EventPriority.DEFAULT;
|
||||
|
||||
/**
|
||||
* Invokes the event listener.
|
||||
*
|
||||
* @param arguments arguments passed along by the event
|
||||
* @since v1-alpha5
|
||||
*/
|
||||
public abstract void run(Object... arguments) throws Exception;
|
||||
}
|
|
@ -20,6 +20,7 @@
|
|||
package de.staropensource.sosengine.base.implementable.helper;
|
||||
|
||||
import de.staropensource.sosengine.base.EngineConfiguration;
|
||||
import de.staropensource.sosengine.base.EngineInternals;
|
||||
import de.staropensource.sosengine.base.annotation.EventListener;
|
||||
import de.staropensource.sosengine.base.event.LogEvent;
|
||||
import de.staropensource.sosengine.base.exception.reflection.InstanceMethodFromStaticContextException;
|
||||
|
@ -27,9 +28,10 @@ import de.staropensource.sosengine.base.exception.reflection.InvalidMethodSignat
|
|||
import de.staropensource.sosengine.base.exception.reflection.NoAccessException;
|
||||
import de.staropensource.sosengine.base.exception.reflection.StaticInitializerException;
|
||||
import de.staropensource.sosengine.base.implementable.Event;
|
||||
import de.staropensource.sosengine.base.implementable.EventListenerCode;
|
||||
import de.staropensource.sosengine.base.internal.implementation.EventListenerMethod;
|
||||
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.type.EventPriority;
|
||||
import de.staropensource.sosengine.base.utility.ListFormatter;
|
||||
import lombok.Getter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
@ -41,10 +43,7 @@ import org.reflections.util.ConfigurationBuilder;
|
|||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Simplifies event logging and calling.
|
||||
|
@ -53,14 +52,6 @@ import java.util.Set;
|
|||
*/
|
||||
@Getter
|
||||
public final class EventHelper {
|
||||
/**
|
||||
* Holds all cached events.
|
||||
* Should not be modified manually.
|
||||
*
|
||||
* @since v1-alpha0
|
||||
*/
|
||||
private static final @NotNull HashMap<@NotNull Class<? extends Event>, LinkedList<@NotNull ReflectionMethod>> cachedEventListeners = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Contains the {@link LoggerInstance} for this instance.
|
||||
*
|
||||
|
@ -69,60 +60,111 @@ public final class EventHelper {
|
|||
*/
|
||||
private static final @NotNull LoggerInstance logger = new LoggerInstance.Builder().setClazz(EventHelper.class).setOrigin("ENGINE").build();
|
||||
|
||||
/**
|
||||
* Holds all cached events.
|
||||
*
|
||||
* @since v1-alpha5
|
||||
*/
|
||||
private static final @NotNull Map<@NotNull Class<? extends Event>, LinkedList<@NotNull EventListenerCode>> cachedEventListeners = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Constructs this class.
|
||||
*
|
||||
* @since v1-alpha0
|
||||
*/
|
||||
public EventHelper() {}
|
||||
private EventHelper() {}
|
||||
|
||||
/**
|
||||
* Returns all {@link EventListener}s listening on some event.
|
||||
* The classpath will be scanned for listeners, unless cached results exist and {@code !forceScanning}.
|
||||
* Registers a new {@link Event}.
|
||||
* <p>
|
||||
* This method does nothing if classpath searching is disabled.
|
||||
*
|
||||
* @param event event class
|
||||
* @param forceScanning forces scanning the classpath for listeners. not recommended due to a huge performance penalty
|
||||
* @return list of event listeners
|
||||
* @see #cacheEvent(Class)
|
||||
* @since v1-alpha0
|
||||
* @param event {@link Event} to register for
|
||||
* @param eventListener {@link EventListenerCode} to register
|
||||
* @param priority priority of the listener
|
||||
* @see EngineInternals#getReflectiveClasspathScanning()
|
||||
* @since v1-alpha5
|
||||
*/
|
||||
public static @NotNull LinkedList<ReflectionMethod> getAnnotatedMethods(@NotNull Class<? extends Event> event, boolean forceScanning) {
|
||||
LinkedList<ReflectionMethod> methods = new LinkedList<>();
|
||||
public static synchronized void registerEvent(@NotNull Class<? extends Event> event, @NotNull EventListenerCode eventListener, @NotNull EventPriority priority) {
|
||||
if (EngineInternals.getInstance().getReflectiveClasspathScanning())
|
||||
return;
|
||||
|
||||
if (forceScanning || !cachedEventListeners.containsKey(event)) {
|
||||
Reflections reflections = new Reflections(
|
||||
new ConfigurationBuilder()
|
||||
.setUrls(ClasspathHelper.forJavaClassPath())
|
||||
.setScanners(Scanners.MethodsAnnotated)
|
||||
);
|
||||
// Update 'eventListener' priority
|
||||
eventListener.priority = priority;
|
||||
|
||||
// Get annotated methods
|
||||
Set<@NotNull Method> annotatedMethods = reflections.getMethodsAnnotatedWith(EventListener.class);
|
||||
// Check if event already exists in map
|
||||
// If not, create entry with a LinkedList
|
||||
if (cachedEventListeners.containsKey(event))
|
||||
if (cachedEventListeners.get(event).contains(eventListener))
|
||||
return;
|
||||
else
|
||||
cachedEventListeners.get(event).add(eventListener);
|
||||
else {
|
||||
LinkedList<@NotNull EventListenerCode> list = new LinkedList<>();
|
||||
list.add(eventListener);
|
||||
cachedEventListeners.put(event, list);
|
||||
}
|
||||
|
||||
// Sort event listeners not listening for this event out
|
||||
for (Method method : annotatedMethods)
|
||||
if (method.getAnnotation(EventListener.class).event() == event)
|
||||
methods.add(Reflect.reflectOn(method));
|
||||
|
||||
// Sort 'methods' linked list after event priority
|
||||
methods.sort(Comparator.comparing(method -> method.getAnnotation(EventListener.class).priority()));
|
||||
} else
|
||||
// Event listeners are cached and !forceScanning, return cached results
|
||||
methods = cachedEventListeners.get(event);
|
||||
|
||||
return methods;
|
||||
logger.diag("Registered event listener " + eventListener + " for event " + event + " with priority " + priority.name());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all {@link EventListener}s listening on some event.
|
||||
* The classpath will be scanned for listeners, unless cached results exist.
|
||||
* Registers a new {@link Event}.
|
||||
* <p>
|
||||
* This method does nothing if classpath searching is disabled.
|
||||
*
|
||||
* @param event event class
|
||||
* @return list of event listeners
|
||||
* @since v1-alpha0
|
||||
* @param event {@link Event} to register for
|
||||
* @param eventListener {@link EventListenerCode} to register
|
||||
* @see EngineInternals#getReflectiveClasspathScanning()
|
||||
* @since v1-alpha5
|
||||
*/
|
||||
public static @NotNull LinkedList<ReflectionMethod> getAnnotatedMethods(@NotNull Class<? extends Event> event) {
|
||||
return getAnnotatedMethods(event, false);
|
||||
public static void registerEvent(@NotNull Class<? extends Event> event, @NotNull EventListenerCode eventListener) {
|
||||
registerEvent(event, eventListener, EventPriority.DEFAULT);
|
||||
}
|
||||
|
||||
/**
|
||||
* (Re-)Caches all event listeners for some {@link Event}.
|
||||
* <p>
|
||||
* This method does nothing if classpath searching is enabled.
|
||||
*
|
||||
* @param event event to (re-)cache. Set to {@code null} to recompute all cached events
|
||||
* @see EngineInternals#getReflectiveClasspathScanning()
|
||||
* @since v1-alpha5
|
||||
*/
|
||||
public static synchronized void cacheEvent(@Nullable Class<? extends Event> event) {
|
||||
if (!EngineInternals.getInstance().getReflectiveClasspathScanning())
|
||||
return;
|
||||
|
||||
if (event == null)
|
||||
for (Class<? extends Event> cachedEvent : cachedEventListeners.keySet())
|
||||
cacheEvent(cachedEvent);
|
||||
else {
|
||||
LinkedList<@NotNull EventListenerCode> annotatedMethods = getAnnotatedMethods(event);
|
||||
|
||||
if (cachedEventListeners.containsKey(event))
|
||||
cachedEventListeners.replace(event, annotatedMethods);
|
||||
else
|
||||
cachedEventListeners.put(event, annotatedMethods);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an event from the event listener cache.
|
||||
* <p>
|
||||
* This method does nothing if classpath searching is enabled.
|
||||
*
|
||||
* @param event event to uncache. Set to {@code null} to clear the entire cache
|
||||
* @see EngineInternals#getReflectiveClasspathScanning()
|
||||
* @since v1-alpha5
|
||||
*/
|
||||
public static synchronized void uncacheEvent(@Nullable Class<? extends Event> event) {
|
||||
if (!EngineInternals.getInstance().getReflectiveClasspathScanning())
|
||||
return;
|
||||
|
||||
if (event == null)
|
||||
cachedEventListeners.clear();
|
||||
else
|
||||
cachedEventListeners.remove(event);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -130,7 +172,7 @@ public final class EventHelper {
|
|||
*
|
||||
* @param event event class
|
||||
* @param arguments arguments to pass to event listeners
|
||||
* @since v1-alpha0
|
||||
* @since v1-alpha5
|
||||
*/
|
||||
public static void invokeAnnotatedMethods(@NotNull Class<? extends Event> event, Object... arguments) {
|
||||
if (event != LogEvent.class && EngineConfiguration.getInstance().isDebugEvents())
|
||||
|
@ -140,21 +182,21 @@ public final class EventHelper {
|
|||
logger.diag("Event " + event.getName() + " was emitted, passing arguments " + ListFormatter.formatArray(arguments));
|
||||
|
||||
Runnable eventCode = () -> {
|
||||
for (ReflectionMethod method : getAnnotatedMethods(event)) {
|
||||
for (EventListenerCode eventListener : getAnnotatedMethods(event)) {
|
||||
try {
|
||||
method.invoke(arguments);
|
||||
eventListener.run(arguments);
|
||||
} catch (NoAccessException exception) {
|
||||
logger.warn("Event listener method " + method.getName() + " could not be called as the method could not be accessed");
|
||||
logger.warn("Event listener " + eventListener + " could not be called as the method could not be accessed");
|
||||
} catch (InvalidMethodSignatureException exception) {
|
||||
logger.warn("Event listener method " + method.getName() + " has an invalid method signature");
|
||||
logger.warn("Event listener " + eventListener + " has an invalid method signature");
|
||||
} catch (InvocationTargetException exception) {
|
||||
logger.crash("Event listener method " + method.getName() + " threw a throwable", exception.getTargetException(), true);
|
||||
logger.crash("Event listener " + eventListener + " 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.");
|
||||
logger.warn("Event listener " + eventListener + " 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);
|
||||
logger.crash("Event listener " + eventListener + " could not be called as the static initializer failed", exception.getThrowable(), true);
|
||||
} catch (Exception exception) {
|
||||
logger.crash("Event listener method " + method.getName() + " could not be called as an error occurred during reflection", exception, true);
|
||||
logger.crash("Event listener " + eventListener + " could not be called as an error occurred during reflection", exception, true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -169,35 +211,54 @@ public final class EventHelper {
|
|||
}
|
||||
|
||||
/**
|
||||
* (Re-)Caches all event listeners for some {@link Event}.
|
||||
* Returns all {@link EventListener}s listening on some event.
|
||||
* The classpath will be scanned for listeners, unless cached results exist and {@code !forceScanning}.
|
||||
*
|
||||
* @param event event to (re-)cache. Set to {@code null} to recompute all cached events
|
||||
* @since v1-alpha0
|
||||
* @param event event class
|
||||
* @param forceScanning forces scanning the classpath for listeners. not recommended due to a huge performance penalty
|
||||
* @return list of event listeners
|
||||
* @see #cacheEvent(Class)
|
||||
* @since v1-alpha5
|
||||
*/
|
||||
public static synchronized void cacheEvent(@Nullable Class<? extends Event> event) {
|
||||
if (event == null)
|
||||
for (Class<? extends Event> cachedEvent : cachedEventListeners.keySet())
|
||||
cacheEvent(cachedEvent);
|
||||
else {
|
||||
LinkedList<@NotNull ReflectionMethod> annotatedMethods = getAnnotatedMethods(event);
|
||||
public static @NotNull LinkedList<EventListenerCode> getAnnotatedMethods(@NotNull Class<? extends Event> event, boolean forceScanning) {
|
||||
LinkedList<EventListenerCode> eventListeners = new LinkedList<>();
|
||||
|
||||
if (cachedEventListeners.containsKey(event))
|
||||
cachedEventListeners.replace(event, annotatedMethods);
|
||||
else
|
||||
cachedEventListeners.put(event, annotatedMethods);
|
||||
}
|
||||
if (!EngineInternals.getInstance().getReflectiveClasspathScanning())
|
||||
return Objects.requireNonNullElse(cachedEventListeners.get(event), eventListeners);
|
||||
|
||||
if (forceScanning || !cachedEventListeners.containsKey(event)) {
|
||||
Reflections reflections = new Reflections(
|
||||
new ConfigurationBuilder()
|
||||
.setUrls(ClasspathHelper.forJavaClassPath())
|
||||
.setScanners(Scanners.MethodsAnnotated)
|
||||
);
|
||||
|
||||
// Get annotated methods
|
||||
Set<@NotNull Method> annotatedMethods = reflections.getMethodsAnnotatedWith(EventListener.class);
|
||||
|
||||
// Sort event listeners not listening for the specified event out
|
||||
for (Method method : annotatedMethods)
|
||||
if (method.getAnnotation(EventListener.class).event() == event)
|
||||
eventListeners.add(new EventListenerMethod(method));
|
||||
|
||||
// Sort list after event priority
|
||||
eventListeners.sort(Comparator.comparing(method -> Objects.requireNonNull(((EventListenerMethod) method).getAnnotation(EventListener.class)).priority()));
|
||||
} else
|
||||
// Event listeners are cached and !forceScanning, return cached results
|
||||
eventListeners = cachedEventListeners.get(event);
|
||||
|
||||
return eventListeners;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an event from the event listener cache.
|
||||
* Returns all {@link EventListener}s listening on some event.
|
||||
* The classpath will be scanned for listeners, unless cached results exist.
|
||||
*
|
||||
* @param event event to uncache. Set to {@code null} to clear the entire cache
|
||||
* @since v1-alpha0
|
||||
* @param event event class
|
||||
* @return list of event listeners
|
||||
* @since v1-alpha5
|
||||
*/
|
||||
public static synchronized void uncacheEvent(@Nullable Class<? extends Event> event) {
|
||||
if (event == null)
|
||||
cachedEventListeners.clear();
|
||||
else
|
||||
cachedEventListeners.remove(event);
|
||||
public static @NotNull LinkedList<EventListenerCode> getAnnotatedMethods(@NotNull Class<? extends Event> event) {
|
||||
return getAnnotatedMethods(event, false);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* STAROPENSOURCE ENGINE SOURCE FILE
|
||||
* Copyright (c) 2024 The StarOpenSource Engine Authors
|
||||
* 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.internal.implementation;
|
||||
|
||||
import de.staropensource.sosengine.base.implementable.EventListenerCode;
|
||||
import de.staropensource.sosengine.base.reflection.Reflect;
|
||||
import de.staropensource.sosengine.base.reflection.ReflectionMethod;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Interface specifically for executing event listener methods.
|
||||
*
|
||||
* @since v1-alpha0
|
||||
*/
|
||||
public final class EventListenerMethod extends EventListenerCode {
|
||||
/**
|
||||
* Contains the method to call and get.
|
||||
*
|
||||
* @since v1-alpha0
|
||||
*/
|
||||
private final @NotNull ReflectionMethod method;
|
||||
|
||||
/**
|
||||
* Constructs this class.
|
||||
*
|
||||
* @since v1-alpha0
|
||||
*/
|
||||
public EventListenerMethod(@NotNull Method method) {
|
||||
this.method = Reflect.reflectOn(method);
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public void run(Object[] arguments) throws Exception {
|
||||
method.invoke(arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Forwards {@link ReflectionMethod#getAnnotation(Class)}
|
||||
* to the internal {@link ReflectionMethod} instance.
|
||||
*
|
||||
* @param annotation annotation to get
|
||||
* @return annotation or {@code null} on error
|
||||
* @see ReflectionMethod#getAnnotation(Class)
|
||||
* @since v1-alpha0
|
||||
*/
|
||||
public <T extends Annotation> @Nullable T getAnnotation(@NotNull Class<T> annotation) {
|
||||
try {
|
||||
return method.getAnnotation(annotation);
|
||||
} catch (NullPointerException exception) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public String toString() {
|
||||
return "method " + method.getMethod().getDeclaringClass().getName() + "#" + method.getName();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* STAROPENSOURCE ENGINE SOURCE FILE
|
||||
* Copyright (c) 2024 The StarOpenSource Engine Authors
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Interfaces and abstract classes which can be used for implementing classes.
|
||||
*
|
||||
* @since v1-alpha0
|
||||
*/
|
||||
package de.staropensource.sosengine.base.internal.implementation;
|
|
@ -20,6 +20,7 @@
|
|||
package de.staropensource.sosengine.base.type;
|
||||
|
||||
import de.staropensource.sosengine.base.EngineInternals;
|
||||
import de.staropensource.sosengine.base.implementable.Event;
|
||||
import de.staropensource.sosengine.base.implementable.ShutdownHandler;
|
||||
|
||||
/**
|
||||
|
@ -51,13 +52,23 @@ public enum InternalAccessArea {
|
|||
*/
|
||||
ALL_READ,
|
||||
|
||||
/**
|
||||
* Refers to all essential read-only areas.
|
||||
* <p>
|
||||
* Essential read-only areas are IIAs which are
|
||||
* very important and should not be restricted.
|
||||
*
|
||||
* @since v1-alpha5
|
||||
*/
|
||||
ALL_READ_ESSENTIAL,
|
||||
|
||||
/**
|
||||
* Refers to the toggling of the JVM shutdown hook, which
|
||||
* prevents JVM shutdowns without the engine first shutting down.
|
||||
*
|
||||
* @since v1-alpha4
|
||||
*/
|
||||
SAFETY_SHUTDOWN_HOOK,
|
||||
SAFETY_SHUTDOWN_HOOK_UPDATE,
|
||||
|
||||
/**
|
||||
* Refers to the getting of the engine's shutdown handler.
|
||||
|
@ -75,7 +86,23 @@ public enum InternalAccessArea {
|
|||
*
|
||||
* @since v1-alpha4
|
||||
*/
|
||||
SHUTDOWN_HANDLER_UPDATE;
|
||||
SHUTDOWN_HANDLER_UPDATE,
|
||||
|
||||
/**
|
||||
* Refers to the getting of the flag controlling whether
|
||||
* automatic {@link Event} classpath searching should be performed.
|
||||
*
|
||||
* @since v1-alpha5
|
||||
*/
|
||||
REFLECTIVE_CLASSPATH_SCANNING_GET,
|
||||
|
||||
/**
|
||||
* Refers to the overriding of the flag controlling whether
|
||||
* automatic {@link Event} classpath searching should be performed.
|
||||
*
|
||||
* @since v1-alpha5
|
||||
*/
|
||||
REFLECTIVE_CLASSPATH_SCANNING_OVERRIDE;
|
||||
|
||||
/**
|
||||
* Returns all read-only areas.
|
||||
|
@ -89,6 +116,18 @@ public enum InternalAccessArea {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all essential read-only areas.
|
||||
*
|
||||
* @return array containing all essential read-only areas
|
||||
* @since v1-alpha5
|
||||
*/
|
||||
public static InternalAccessArea[] valuesEssentialReadOnly() {
|
||||
return new InternalAccessArea[]{
|
||||
REFLECTIVE_CLASSPATH_SCANNING_GET,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all write-only areas.
|
||||
*
|
||||
|
@ -97,8 +136,9 @@ public enum InternalAccessArea {
|
|||
*/
|
||||
public static InternalAccessArea[] valuesWriteOnly() {
|
||||
return new InternalAccessArea[]{
|
||||
SAFETY_SHUTDOWN_HOOK,
|
||||
SAFETY_SHUTDOWN_HOOK_UPDATE,
|
||||
SHUTDOWN_HANDLER_UPDATE,
|
||||
REFLECTIVE_CLASSPATH_SCANNING_OVERRIDE,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -81,6 +81,10 @@ application {
|
|||
// Force writing to standard output
|
||||
"-Dsosengine.base.loggerForceStandardOutput=true",
|
||||
|
||||
// Pass classes which should be included if
|
||||
// reflective sclasspath scanning is disabled.
|
||||
"-Dsosengine.base.initialIncludeSubsystemClasses=de.staropensource.sosengine.ansi.AnsiSubsystem,de.staropensource.sosengine.slf4j_compat.Slf4jCompatSubsystem,de.staropensource.sosengine.windowing.glfw.GlfwSubsystem",
|
||||
|
||||
// Force Jansi to write escape sequences
|
||||
"-Djansi.mode=force",
|
||||
]
|
||||
|
@ -121,7 +125,9 @@ tasks.register('runNativeImage', Exec) {
|
|||
args(
|
||||
"-Dsosengine.base.loggerLevel=diagnostic",
|
||||
"-Dsosengine.base.loggerForceStandardOutput=true",
|
||||
"-Djansi.mode=force"
|
||||
"-Dsosengine.base.initialForceDisableClasspathScanning=true",
|
||||
"-Dsosengine.base.initialIncludeSubsystemClasses=de.staropensource.sosengine.ansi.AnsiSubsystem,de.staropensource.sosengine.slf4j_compat.Slf4jCompatSubsystem,de.staropensource.sosengine.windowing.glfw.GlfwSubsystem",
|
||||
"-Djansi.mode=force",
|
||||
)
|
||||
executable("build/bin/sosengine-testapp")
|
||||
}
|
||||
|
|
|
@ -21,6 +21,8 @@ package de.staropensource.sosengine.testapp;
|
|||
|
||||
import de.staropensource.sosengine.base.Engine;
|
||||
import de.staropensource.sosengine.base.annotation.EventListener;
|
||||
import de.staropensource.sosengine.base.implementable.EventListenerCode;
|
||||
import de.staropensource.sosengine.base.implementable.helper.EventHelper;
|
||||
import de.staropensource.sosengine.base.logging.LoggerInstance;
|
||||
import de.staropensource.sosengine.base.type.vector.Vec2i;
|
||||
import de.staropensource.sosengine.base.utility.Miscellaneous;
|
||||
|
@ -113,9 +115,25 @@ public final class Main {
|
|||
@SneakyThrows
|
||||
public void run() {
|
||||
try {
|
||||
// Specify subsystems to load
|
||||
System.setProperty(
|
||||
"sosengine.base.initialIncludeSubsystemClasses", (
|
||||
System.getProperty("sosengine.base.initialIncludeSubsystemClasses") == null
|
||||
? "" : System.getProperty("sosengine.base.initialIncludeSubsystemClasses") + ","
|
||||
) + "de.staropensource.sosengine.windowing.WindowingSubsystem"
|
||||
);
|
||||
|
||||
// Initialize sos!engine
|
||||
engine = new Engine();
|
||||
|
||||
// Register events
|
||||
EventHelper.registerEvent(InputEvent.class, new EventListenerCode() {
|
||||
@Override
|
||||
public void run(Object... arguments) {
|
||||
onInput((Window) arguments[0], (Key) arguments[1], (KeyState) arguments[2]);
|
||||
}
|
||||
});
|
||||
|
||||
// Say hello to the world!
|
||||
logger.info("Hello world!");
|
||||
|
||||
|
|
Loading…
Reference in a new issue