From da4df1f3c50e497bba5e632bc1511b0194d9ea06 Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Fri, 31 May 2024 11:34:45 +0100 Subject: [PATCH 1/2] Added support for @Inject to work on constructors --- .../jdi/core/annotations/Inject.java | 2 +- .../processor/SlashCommandClassMethod.java | 2 +- .../processor/loader/ComponentLoader.java | 2 +- .../processor/loader/ListenerLoader.java | 32 +++++++++--- .../processor/loader/SlashCommandLoader.java | 51 +++++++++++++------ .../validator/EventListenerValidator.java | 16 ------ .../com/javadiscord/jdi/core/Discord.java | 12 ++--- .../interaction/InteractionEventHandler.java | 30 +++-------- .../ReflectiveSlashCommandClassMethod.java | 2 + .../bot/commands/slash/ChatGPTCommand.java | 5 +- 10 files changed, 82 insertions(+), 72 deletions(-) diff --git a/annotations/src/main/java/com/javadiscord/jdi/core/annotations/Inject.java b/annotations/src/main/java/com/javadiscord/jdi/core/annotations/Inject.java index 67aebb96..b15b4356 100644 --- a/annotations/src/main/java/com/javadiscord/jdi/core/annotations/Inject.java +++ b/annotations/src/main/java/com/javadiscord/jdi/core/annotations/Inject.java @@ -6,5 +6,5 @@ import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.FIELD}) +@Target({ElementType.FIELD, ElementType.CONSTRUCTOR}) public @interface Inject {} diff --git a/annotations/src/main/java/com/javadiscord/jdi/internal/processor/SlashCommandClassMethod.java b/annotations/src/main/java/com/javadiscord/jdi/internal/processor/SlashCommandClassMethod.java index 02a8cb5f..edc73e09 100644 --- a/annotations/src/main/java/com/javadiscord/jdi/internal/processor/SlashCommandClassMethod.java +++ b/annotations/src/main/java/com/javadiscord/jdi/internal/processor/SlashCommandClassMethod.java @@ -2,4 +2,4 @@ import java.lang.reflect.Method; -public record SlashCommandClassMethod(Class clazz, Method method) {} +public record SlashCommandClassMethod(Class clazz, Method method, Object instance) {} diff --git a/annotations/src/main/java/com/javadiscord/jdi/internal/processor/loader/ComponentLoader.java b/annotations/src/main/java/com/javadiscord/jdi/internal/processor/loader/ComponentLoader.java index 2ad035c3..3cc43676 100644 --- a/annotations/src/main/java/com/javadiscord/jdi/internal/processor/loader/ComponentLoader.java +++ b/annotations/src/main/java/com/javadiscord/jdi/internal/processor/loader/ComponentLoader.java @@ -19,7 +19,7 @@ public class ComponentLoader { private static final Logger LOGGER = LogManager.getLogger(ComponentLoader.class); - private static final Map, Object> COMPONENTS = new HashMap<>(); + public static final Map, Object> COMPONENTS = new HashMap<>(); private final ComponentValidator componentValidator = new ComponentValidator(); public void loadComponents() { diff --git a/annotations/src/main/java/com/javadiscord/jdi/internal/processor/loader/ListenerLoader.java b/annotations/src/main/java/com/javadiscord/jdi/internal/processor/loader/ListenerLoader.java index 2f0a5444..b664f977 100644 --- a/annotations/src/main/java/com/javadiscord/jdi/internal/processor/loader/ListenerLoader.java +++ b/annotations/src/main/java/com/javadiscord/jdi/internal/processor/loader/ListenerLoader.java @@ -2,10 +2,11 @@ import java.io.File; import java.lang.reflect.Constructor; +import java.lang.reflect.Parameter; +import java.util.ArrayList; import java.util.List; import com.javadiscord.jdi.core.annotations.EventListener; -import com.javadiscord.jdi.internal.exceptions.NoZeroArgConstructorException; import com.javadiscord.jdi.internal.processor.ClassFileUtil; import com.javadiscord.jdi.internal.processor.validator.EventListenerValidator; @@ -16,9 +17,11 @@ public class ListenerLoader { private static final Logger LOGGER = LogManager.getLogger(ListenerLoader.class); private final EventListenerValidator eventListenerValidator = new EventListenerValidator(); private final List eventListeners; + private final ComponentLoader componentLoader; - public ListenerLoader(List eventListeners) { + public ListenerLoader(List eventListeners, ComponentLoader componentLoader) { this.eventListeners = eventListeners; + this.componentLoader = componentLoader; try { loadListeners(); } catch (Exception e) { @@ -46,7 +49,13 @@ public void loadListeners() { private void registerListener(Class clazz) { try { - Object instance = getZeroArgConstructor(clazz).newInstance(); + Constructor constructor = clazz.getConstructors()[0]; + Object instance; + if (constructor.getParameterCount() > 0) { + instance = constructor.newInstance(getConstructorParameters(constructor).toArray()); + } else { + instance = constructor.newInstance(); + } ComponentLoader.injectComponents(instance); eventListeners.add(instance); LOGGER.info("Registered listener {}", clazz.getName()); @@ -55,6 +64,19 @@ private void registerListener(Class clazz) { } } + private List getConstructorParameters(Constructor constructor) { + List constructorParameters = new ArrayList<>(); + for (Parameter parameter : constructor.getParameters()) { + if (ComponentLoader.COMPONENTS.containsKey(parameter.getType())) { + constructorParameters.add(ComponentLoader.COMPONENTS.get(parameter.getType())); + } else { + constructorParameters.add(null); + LOGGER.warn("No component found for {}", parameter.getType()); + } + } + return constructorParameters; + } + public boolean validateListener(Class clazz) { return eventListenerValidator.validate(clazz); } @@ -66,8 +88,6 @@ public static Constructor getZeroArgConstructor(Class clazz) { return constructor; } } - throw new NoZeroArgConstructorException( - "No zero arg constructor found for " + clazz.getName() - ); + return null; } } diff --git a/annotations/src/main/java/com/javadiscord/jdi/internal/processor/loader/SlashCommandLoader.java b/annotations/src/main/java/com/javadiscord/jdi/internal/processor/loader/SlashCommandLoader.java index 128bd85c..ac9b7e08 100644 --- a/annotations/src/main/java/com/javadiscord/jdi/internal/processor/loader/SlashCommandLoader.java +++ b/annotations/src/main/java/com/javadiscord/jdi/internal/processor/loader/SlashCommandLoader.java @@ -3,6 +3,8 @@ import java.io.File; import java.lang.reflect.Constructor; import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -34,9 +36,10 @@ private void loadInteractionListeners() { Method[] methods = clazz.getMethods(); for (Method method : methods) { if (method.getAnnotation(SlashCommand.class) != null) { - if (validator.validate(method) && hasZeroArgsConstructor(clazz)) { + if (validator.validate(method)) { registerListener( - clazz, method, method.getAnnotation(SlashCommand.class).name() + clazz, method, method.getAnnotation(SlashCommand.class).name(), + createInstance(clazz) ); } else { throw new ValidationException(method.getName() + " failed validation"); @@ -49,7 +52,36 @@ private void loadInteractionListeners() { } } - private void registerListener(Class clazz, Method method, String name) { + private Object createInstance(Class clazz) { + Object instance = null; + try { + Constructor constructor = clazz.getConstructors()[0]; + if (constructor.getParameterCount() > 0) { + instance = constructor.newInstance(getConstructorParameters(constructor).toArray()); + } else { + instance = constructor.newInstance(); + } + injectComponents(instance); + } catch (Exception e) { + LOGGER.error("Failed to create {} instance", clazz.getName(), e); + } + return instance; + } + + private List getConstructorParameters(Constructor constructor) { + List constructorParameters = new ArrayList<>(); + for (Parameter parameter : constructor.getParameters()) { + if (ComponentLoader.COMPONENTS.containsKey(parameter.getType())) { + constructorParameters.add(ComponentLoader.COMPONENTS.get(parameter.getType())); + } else { + constructorParameters.add(null); + LOGGER.warn("No component found for {}", parameter.getType()); + } + } + return constructorParameters; + } + + private void registerListener(Class clazz, Method method, String name, Object instance) { try { if (interactionListeners.containsKey(name)) { LOGGER.error( @@ -60,7 +92,7 @@ private void registerListener(Class clazz, Method method, String name) { ); return; } - interactionListeners.put(name, new SlashCommandClassMethod(clazz, method)); + interactionListeners.put(name, new SlashCommandClassMethod(clazz, method, instance)); LOGGER.info("Found slash command handler {}", clazz.getName()); } catch (Exception e) { LOGGER.error("Failed to create {} instance", clazz.getName(), e); @@ -71,17 +103,6 @@ public SlashCommandClassMethod getSlashCommandClassMethod(String name) { return interactionListeners.get(name); } - private boolean hasZeroArgsConstructor(Class clazz) { - Constructor[] constructors = clazz.getConstructors(); - for (Constructor constructor : constructors) { - if (constructor.getParameterCount() == 0) { - return true; - } - } - LOGGER.error("{} does not have a 0 arg constructor", clazz.getName()); - return false; - } - public void injectComponents(Object object) { ComponentLoader.injectComponents(object); } diff --git a/annotations/src/main/java/com/javadiscord/jdi/internal/processor/validator/EventListenerValidator.java b/annotations/src/main/java/com/javadiscord/jdi/internal/processor/validator/EventListenerValidator.java index 1cdc3766..ca1d690a 100644 --- a/annotations/src/main/java/com/javadiscord/jdi/internal/processor/validator/EventListenerValidator.java +++ b/annotations/src/main/java/com/javadiscord/jdi/internal/processor/validator/EventListenerValidator.java @@ -1,7 +1,6 @@ package com.javadiscord.jdi.internal.processor.validator; import java.lang.annotation.Annotation; -import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; @@ -388,21 +387,6 @@ public class EventListenerValidator { } public boolean validate(Class clazz) { - return hasZeroArgsConstructor(clazz) && validateMethods(clazz); - } - - public boolean hasZeroArgsConstructor(Class clazz) { - Constructor[] constructors = clazz.getConstructors(); - for (Constructor constructor : constructors) { - if (constructor.getParameterCount() == 0) { - return true; - } - } - LOGGER.error("{} does not have a 0 arg constructor", clazz.getName()); - return false; - } - - private boolean validateMethods(Class clazz) { Method[] methods = clazz.getMethods(); for (Method method : methods) { if (!validateMethodAnnotations(method)) { diff --git a/core/src/main/java/com/javadiscord/jdi/core/Discord.java b/core/src/main/java/com/javadiscord/jdi/core/Discord.java index 1640ca7e..cc1c9792 100644 --- a/core/src/main/java/com/javadiscord/jdi/core/Discord.java +++ b/core/src/main/java/com/javadiscord/jdi/core/Discord.java @@ -68,6 +68,7 @@ public class Discord { private WebSocketManager webSocketManager; private long applicationId; private boolean started = false; + private Object componentLoader; public Discord(String botToken) { this( @@ -236,9 +237,10 @@ private void loadComponents() { ReflectiveComponentLoader componentLoader = null; for (Constructor constructor : clazz.getConstructors()) { if (constructor.getParameterCount() == 0) { + this.componentLoader = constructor.newInstance(); componentLoader = ReflectiveLoader - .proxy(constructor.newInstance(), ReflectiveComponentLoader.class); + .proxy(this.componentLoader, ReflectiveComponentLoader.class); } } if (componentLoader != null) { @@ -257,12 +259,8 @@ private void loadAnnotations() { Class clazz = Class.forName(Constants.LISTENER_LOADER_CLASS); for (Constructor constructor : clazz.getConstructors()) { - if (constructor.getParameterCount() == 1) { - Parameter parameters = constructor.getParameters()[0]; - if (parameters.getType().equals(List.class)) { - constructor.newInstance(annotatedEventListeners); - return; - } + if (constructor.getParameterCount() == 2) { + constructor.newInstance(annotatedEventListeners, componentLoader); } } } catch (Exception e) { diff --git a/core/src/main/java/com/javadiscord/jdi/core/interaction/InteractionEventHandler.java b/core/src/main/java/com/javadiscord/jdi/core/interaction/InteractionEventHandler.java index 70ba19db..abe4bd31 100644 --- a/core/src/main/java/com/javadiscord/jdi/core/interaction/InteractionEventHandler.java +++ b/core/src/main/java/com/javadiscord/jdi/core/interaction/InteractionEventHandler.java @@ -22,8 +22,6 @@ public class InteractionEventHandler implements EventListener { private final Object slashCommandLoader; private final Discord discord; - private final Map cachedInstances = new HashMap<>(); - public InteractionEventHandler(Object slashCommandLoader, Discord discord) { this.slashCommandLoader = slashCommandLoader; this.discord = discord; @@ -43,13 +41,12 @@ public void onInteractionCreate(Interaction interaction, Guild guild) { ReflectiveSlashCommandClassMethod.class ); - Class handler = reflectiveSlashCommandClassMethod.clazz(); Method method = reflectiveSlashCommandClassMethod.method(); - + Object instance = reflectiveSlashCommandClassMethod.instance(); List paramOrder = getOrderOfParameters(method, interaction); if (validateParameterCount(method, paramOrder)) { - invokeHandler(handler, method, paramOrder); + invokeHandler(method, paramOrder, instance); } else { throw new InstantiationException( "Bound " + paramOrder.size() + " parameters but expected " @@ -86,31 +83,16 @@ private boolean validateParameterCount(Method method, List paramOrder) { } private void invokeHandler( - Class handler, Method method, - List paramOrder + List paramOrder, + Object instance ) throws InstantiationException { try { - if (cachedInstances.containsKey(handler.getName())) { - method.invoke(cachedInstances.get(handler.getName()), paramOrder.toArray()); - } else { - Object handlerInstance = handler.getDeclaredConstructor().newInstance(); - cachedInstances.put(handler.getName(), handlerInstance); - injectComponents(handlerInstance); - method.invoke(handlerInstance, paramOrder.toArray()); - } + method.invoke(instance, paramOrder.toArray()); } catch ( - InvocationTargetException | IllegalAccessException | NoSuchMethodException - | InstantiationException e + InvocationTargetException | IllegalAccessException e ) { throw new InstantiationException(e.getLocalizedMessage()); } } - - private void injectComponents(Object object) { - ReflectiveSlashCommandLoader reflectiveSlashCommandLoader = - ReflectiveLoader.proxy(slashCommandLoader, ReflectiveSlashCommandLoader.class); - - reflectiveSlashCommandLoader.injectComponents(object); - } } diff --git a/core/src/main/java/com/javadiscord/jdi/internal/ReflectiveSlashCommandClassMethod.java b/core/src/main/java/com/javadiscord/jdi/internal/ReflectiveSlashCommandClassMethod.java index 6cb82e96..526057d7 100644 --- a/core/src/main/java/com/javadiscord/jdi/internal/ReflectiveSlashCommandClassMethod.java +++ b/core/src/main/java/com/javadiscord/jdi/internal/ReflectiveSlashCommandClassMethod.java @@ -6,4 +6,6 @@ public interface ReflectiveSlashCommandClassMethod { Class clazz(); Method method(); + + Object instance(); } diff --git a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/ChatGPTCommand.java b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/ChatGPTCommand.java index 6132f566..22a4a384 100644 --- a/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/ChatGPTCommand.java +++ b/example/lj-discord-bot/src/main/java/com/javadiscord/bot/commands/slash/ChatGPTCommand.java @@ -12,9 +12,12 @@ import com.javadiscord.jdi.core.models.message.embed.EmbedAuthor; public class ChatGPTCommand { + private final ChatGPT chatGPT; @Inject - private ChatGPT chatGPT; + public ChatGPTCommand(ChatGPT chatGPT) { + this.chatGPT = chatGPT; + } @SlashCommand( name = "chatgpt", description = "Ask ChatGPT a question", options = { From 5a1972485eade3bd8fb671c2b1d885ede53d7504 Mon Sep 17 00:00:00 2001 From: Suraj Kumar Date: Fri, 31 May 2024 11:38:45 +0100 Subject: [PATCH 2/2] Removed 0-arg test as no longer needed --- .../jdi/core/processor/EventListenerValidatorTest.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/annotations/src/test/unit/com/javadiscord/jdi/core/processor/EventListenerValidatorTest.java b/annotations/src/test/unit/com/javadiscord/jdi/core/processor/EventListenerValidatorTest.java index 4eeb6cfb..e86fc9eb 100644 --- a/annotations/src/test/unit/com/javadiscord/jdi/core/processor/EventListenerValidatorTest.java +++ b/annotations/src/test/unit/com/javadiscord/jdi/core/processor/EventListenerValidatorTest.java @@ -15,14 +15,6 @@ class EventListenerValidatorTest { - @Test - void testValidationFailsWhenZeroArgConstructorDoesNotExist() { - class Test {} - - EventListenerValidator eventListenerValidator = new EventListenerValidator(); - assertFalse(eventListenerValidator.validate(Test.class)); - } - public static class ClassWithConstructor { public ClassWithConstructor() {} }