From 7cb9fd52f92b7b2d4a9528aaf19e23384e04e0c1 Mon Sep 17 00:00:00 2001 From: Nick Hensel Date: Thu, 16 Jan 2025 22:28:05 +0100 Subject: [PATCH] Move to extension system and add docs --- .../jda/commands/guice/GuiceExtension.java | 29 +++++ .../commands/guice/GuiceExtensionData.java | 13 ++ .../commands/guice/GuiceInstantiatorData.java | 11 -- .../guice/internal/GuiceInstanceProvider.java | 32 +++++ .../guice/internal/GuiceInstantiator.java | 31 ----- .../internal/GuiceInstantiatorProvider.java | 26 ---- guice/src/main/java/module-info.java | 4 +- ...ing.instantiation.spi.InstantiatorProvider | 1 - ...aktushose.jda.commands.extension.Extension | 1 + .../kaktushose/jda/commands/JDACommands.java | 8 +- .../jda/commands/JDACommandsBuilder.java | 123 ++++++++++++++---- .../commands/annotations/Implementation.java | 2 +- .../{handling => }/DispatchingContext.java | 7 +- .../dispatching/JDAEventListener.java | 1 - .../jda/commands/dispatching/Runtime.java | 29 ++--- .../commands/dispatching/events/Event.java | 5 +- .../handling/AutoCompleteHandler.java | 1 + .../handling/ComponentHandler.java | 1 + .../dispatching/handling/EventHandler.java | 3 +- .../dispatching/handling/ModalHandler.java | 1 + .../command/ContextCommandHandler.java | 2 +- .../handling/command/SlashCommandHandler.java | 2 +- .../instance/InstanceProvider.java | 43 ++++++ .../dispatching/instance/package-info.java | 2 + .../instantiation/InstantiationContext.java | 15 --- .../instantiation/Instantiator.java | 10 -- .../spi/InstantiatorProvider.java | 10 -- .../jda/commands/extension/Extension.java | 40 ++++++ .../jda/commands/extension/package-info.java | 5 + jda-commands/src/main/java/module-info.java | 9 +- test/src/main/java/module-info.java | 1 - 31 files changed, 295 insertions(+), 173 deletions(-) create mode 100644 guice/src/main/java/com/github/kaktushose/jda/commands/guice/GuiceExtension.java create mode 100644 guice/src/main/java/com/github/kaktushose/jda/commands/guice/GuiceExtensionData.java delete mode 100644 guice/src/main/java/com/github/kaktushose/jda/commands/guice/GuiceInstantiatorData.java create mode 100644 guice/src/main/java/com/github/kaktushose/jda/commands/guice/internal/GuiceInstanceProvider.java delete mode 100644 guice/src/main/java/com/github/kaktushose/jda/commands/guice/internal/GuiceInstantiator.java delete mode 100644 guice/src/main/java/com/github/kaktushose/jda/commands/guice/internal/GuiceInstantiatorProvider.java delete mode 100644 guice/src/main/resources/META-INF/services/com.github.kaktushose.jda.commands.dispatching.instantiation.spi.InstantiatorProvider create mode 100644 guice/src/main/resources/META-INF/services/com.github.kaktushose.jda.commands.extension.Extension rename jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/{handling => }/DispatchingContext.java (78%) create mode 100644 jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/instance/InstanceProvider.java create mode 100644 jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/instance/package-info.java delete mode 100644 jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/instantiation/InstantiationContext.java delete mode 100644 jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/instantiation/Instantiator.java delete mode 100644 jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/instantiation/spi/InstantiatorProvider.java create mode 100644 jda-commands/src/main/java/com/github/kaktushose/jda/commands/extension/Extension.java create mode 100644 jda-commands/src/main/java/com/github/kaktushose/jda/commands/extension/package-info.java diff --git a/guice/src/main/java/com/github/kaktushose/jda/commands/guice/GuiceExtension.java b/guice/src/main/java/com/github/kaktushose/jda/commands/guice/GuiceExtension.java new file mode 100644 index 00000000..00a14f21 --- /dev/null +++ b/guice/src/main/java/com/github/kaktushose/jda/commands/guice/GuiceExtension.java @@ -0,0 +1,29 @@ +package com.github.kaktushose.jda.commands.guice; + +import com.github.kaktushose.jda.commands.JDACommandsBuilder; +import com.github.kaktushose.jda.commands.dispatching.instance.InstanceProvider; +import com.github.kaktushose.jda.commands.extension.Extension; +import com.github.kaktushose.jda.commands.guice.internal.GuiceInstanceProvider; +import com.google.inject.Guice; +import com.google.inject.Injector; +import org.jetbrains.annotations.NotNull; + +/// The implementation of [Extension] for using Google's [Guice] as an [InstanceProvider]. +/// +/// @see GuiceExtensionData +public class GuiceExtension implements Extension { + + @Override + public void configure(@NotNull JDACommandsBuilder builder, GuiceExtensionData data) { + Injector injector = data != null + ? data.providedInjector() + : Guice.createInjector(); + + builder.instanceProvider(new GuiceInstanceProvider(injector, false)); + } + + @Override + public @NotNull Class dataType() { + return GuiceExtensionData.class; + } +} diff --git a/guice/src/main/java/com/github/kaktushose/jda/commands/guice/GuiceExtensionData.java b/guice/src/main/java/com/github/kaktushose/jda/commands/guice/GuiceExtensionData.java new file mode 100644 index 00000000..c9a8dc48 --- /dev/null +++ b/guice/src/main/java/com/github/kaktushose/jda/commands/guice/GuiceExtensionData.java @@ -0,0 +1,13 @@ +package com.github.kaktushose.jda.commands.guice; + +import com.github.kaktushose.jda.commands.extension.Extension; +import com.google.inject.Injector; +import org.jetbrains.annotations.NotNull; + +/// Custom [Extension.Data] to be used to configure this extension. +/// @param providedInjector The [Injector] to be used instead of creating one +public record GuiceExtensionData( + @NotNull + Injector providedInjector +) implements Extension.Data { +} diff --git a/guice/src/main/java/com/github/kaktushose/jda/commands/guice/GuiceInstantiatorData.java b/guice/src/main/java/com/github/kaktushose/jda/commands/guice/GuiceInstantiatorData.java deleted file mode 100644 index 39a1c966..00000000 --- a/guice/src/main/java/com/github/kaktushose/jda/commands/guice/GuiceInstantiatorData.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.github.kaktushose.jda.commands.guice; - -import com.github.kaktushose.jda.commands.dispatching.instantiation.spi.InstantiatorProvider; -import com.google.inject.Injector; -import org.jetbrains.annotations.NotNull; - -public record GuiceInstantiatorData( - @NotNull - Injector providedInjector -) implements InstantiatorProvider.Data { -} diff --git a/guice/src/main/java/com/github/kaktushose/jda/commands/guice/internal/GuiceInstanceProvider.java b/guice/src/main/java/com/github/kaktushose/jda/commands/guice/internal/GuiceInstanceProvider.java new file mode 100644 index 00000000..db4da760 --- /dev/null +++ b/guice/src/main/java/com/github/kaktushose/jda/commands/guice/internal/GuiceInstanceProvider.java @@ -0,0 +1,32 @@ +package com.github.kaktushose.jda.commands.guice.internal; + +import com.github.kaktushose.jda.commands.annotations.interactions.Interaction; +import com.github.kaktushose.jda.commands.dispatching.instance.InstanceProvider; +import com.google.inject.Injector; + +public class GuiceInstanceProvider implements InstanceProvider { + + private final Injector injector; + private final boolean runtimeBound; + + public GuiceInstanceProvider(Injector injector, boolean runtimeBound) { + this.injector = injector; + this.runtimeBound = runtimeBound; + } + + @Override + public T instance(Class clazz, Context context) { + if (!runtimeBound) { + throw new UnsupportedOperationException("GuiceInstanceProvider must be used runtime bound!"); + } + + return injector.getInstance(clazz); + } + + /// Creates a new child injector with its own [JDACommandsModule] for each runtime. + /// This has the effect, that each class annotated with [Interaction] will be treated as a runtime scoped singleton. + @Override + public InstanceProvider forRuntime(String id) { + return new GuiceInstanceProvider(injector.createChildInjector(new JDACommandsModule()), true); + } +} diff --git a/guice/src/main/java/com/github/kaktushose/jda/commands/guice/internal/GuiceInstantiator.java b/guice/src/main/java/com/github/kaktushose/jda/commands/guice/internal/GuiceInstantiator.java deleted file mode 100644 index f63f2bc9..00000000 --- a/guice/src/main/java/com/github/kaktushose/jda/commands/guice/internal/GuiceInstantiator.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.github.kaktushose.jda.commands.guice.internal; - -import com.github.kaktushose.jda.commands.dispatching.instantiation.InstantiationContext; -import com.github.kaktushose.jda.commands.dispatching.instantiation.Instantiator; -import com.google.inject.Injector; - -public class GuiceInstantiator implements Instantiator { - - private final boolean runtimeBound; - private final Injector injector; - - public GuiceInstantiator(boolean runtimeBound, Injector injector) { - this.runtimeBound = runtimeBound; - this.injector = injector; - } - - @Override - public T instantiate(Class clazz, InstantiationContext context) { - if (!runtimeBound) { - throw new UnsupportedOperationException("Guice Instantiators must be runtime bound!"); - } - - return injector.getInstance(clazz); - } - - @Override - public Instantiator forRuntime(String id) { - Injector runtimeBoundInjector = injector.createChildInjector(new JDACommandsModule()); - return new GuiceInstantiator(true, runtimeBoundInjector); - } -} diff --git a/guice/src/main/java/com/github/kaktushose/jda/commands/guice/internal/GuiceInstantiatorProvider.java b/guice/src/main/java/com/github/kaktushose/jda/commands/guice/internal/GuiceInstantiatorProvider.java deleted file mode 100644 index a180d89a..00000000 --- a/guice/src/main/java/com/github/kaktushose/jda/commands/guice/internal/GuiceInstantiatorProvider.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.github.kaktushose.jda.commands.guice.internal; - -import com.github.kaktushose.jda.commands.dispatching.instantiation.Instantiator; -import com.github.kaktushose.jda.commands.dispatching.instantiation.spi.InstantiatorProvider; -import com.github.kaktushose.jda.commands.guice.GuiceInstantiatorData; -import com.google.inject.Guice; -import com.google.inject.Injector; - -public class GuiceInstantiatorProvider implements InstantiatorProvider { - - @Override - public Instantiator create(InstantiatorProvider.Data data) { - - var injector = switch (data) { - case GuiceInstantiatorData(Injector provided) -> provided; - case null, default -> Guice.createInjector(); - }; - - return new GuiceInstantiator(false, injector); - } - - @Override - public int priority() { - return 0; - } -} diff --git a/guice/src/main/java/module-info.java b/guice/src/main/java/module-info.java index b128c6ef..cb58ae1a 100644 --- a/guice/src/main/java/module-info.java +++ b/guice/src/main/java/module-info.java @@ -1,5 +1,3 @@ -import com.github.kaktushose.jda.commands.guice.internal.GuiceInstantiatorProvider; - module jda.commands.guice { requires transitive jda.commands; //noinspection requires-transitive-automatic -- must be @@ -8,5 +6,5 @@ exports com.github.kaktushose.jda.commands.guice; - provides com.github.kaktushose.jda.commands.dispatching.instantiation.spi.InstantiatorProvider with GuiceInstantiatorProvider; + provides com.github.kaktushose.jda.commands.extension.Extension with com.github.kaktushose.jda.commands.guice.GuiceExtension; } \ No newline at end of file diff --git a/guice/src/main/resources/META-INF/services/com.github.kaktushose.jda.commands.dispatching.instantiation.spi.InstantiatorProvider b/guice/src/main/resources/META-INF/services/com.github.kaktushose.jda.commands.dispatching.instantiation.spi.InstantiatorProvider deleted file mode 100644 index c8116746..00000000 --- a/guice/src/main/resources/META-INF/services/com.github.kaktushose.jda.commands.dispatching.instantiation.spi.InstantiatorProvider +++ /dev/null @@ -1 +0,0 @@ -com.github.kaktushose.jda.commands.guice.internal.GuiceInstantiatorProvider \ No newline at end of file diff --git a/guice/src/main/resources/META-INF/services/com.github.kaktushose.jda.commands.extension.Extension b/guice/src/main/resources/META-INF/services/com.github.kaktushose.jda.commands.extension.Extension new file mode 100644 index 00000000..516c6687 --- /dev/null +++ b/guice/src/main/resources/META-INF/services/com.github.kaktushose.jda.commands.extension.Extension @@ -0,0 +1 @@ +com.github.kaktushose.jda.commands.guice.GuiceExtension \ No newline at end of file diff --git a/jda-commands/src/main/java/com/github/kaktushose/jda/commands/JDACommands.java b/jda-commands/src/main/java/com/github/kaktushose/jda/commands/JDACommands.java index c56f3927..94061382 100644 --- a/jda-commands/src/main/java/com/github/kaktushose/jda/commands/JDACommands.java +++ b/jda-commands/src/main/java/com/github/kaktushose/jda/commands/JDACommands.java @@ -9,11 +9,11 @@ import com.github.kaktushose.jda.commands.definitions.interactions.InteractionRegistry; import com.github.kaktushose.jda.commands.definitions.interactions.component.ButtonDefinition; import com.github.kaktushose.jda.commands.definitions.interactions.component.menu.SelectMenuDefinition; -import com.github.kaktushose.jda.commands.dispatching.instantiation.Instantiator; +import com.github.kaktushose.jda.commands.dispatching.DispatchingContext; import com.github.kaktushose.jda.commands.dispatching.JDAEventListener; import com.github.kaktushose.jda.commands.dispatching.adapter.internal.TypeAdapters; import com.github.kaktushose.jda.commands.dispatching.expiration.ExpirationStrategy; -import com.github.kaktushose.jda.commands.dispatching.handling.DispatchingContext; +import com.github.kaktushose.jda.commands.dispatching.instance.InstanceProvider; import com.github.kaktushose.jda.commands.dispatching.middleware.internal.Middlewares; import com.github.kaktushose.jda.commands.embeds.error.ErrorMessageFactory; import com.github.kaktushose.jda.commands.internal.JDAContext; @@ -48,12 +48,12 @@ public final class JDACommands { ErrorMessageFactory errorMessageFactory, GuildScopeProvider guildScopeProvider, InteractionRegistry interactionRegistry, - Instantiator instantiator, + InstanceProvider instanceProvider, InteractionDefinition.ReplyConfig globalReplyConfig) { this.jdaContext = jdaContext; this.interactionRegistry = interactionRegistry; this.updater = new SlashCommandUpdater(jdaContext, guildScopeProvider, interactionRegistry); - this.jdaEventListener = new JDAEventListener(new DispatchingContext(middlewares, errorMessageFactory, interactionRegistry, typeAdapters, expirationStrategy, instantiator, globalReplyConfig)); + this.jdaEventListener = new JDAEventListener(new DispatchingContext(middlewares, errorMessageFactory, interactionRegistry, typeAdapters, expirationStrategy, instanceProvider, globalReplyConfig)); } JDACommands start(Collection classFinders, Class clazz, String[] packages) { diff --git a/jda-commands/src/main/java/com/github/kaktushose/jda/commands/JDACommandsBuilder.java b/jda-commands/src/main/java/com/github/kaktushose/jda/commands/JDACommandsBuilder.java index 2881ccd7..eb009c3b 100644 --- a/jda-commands/src/main/java/com/github/kaktushose/jda/commands/JDACommandsBuilder.java +++ b/jda-commands/src/main/java/com/github/kaktushose/jda/commands/JDACommandsBuilder.java @@ -8,8 +8,7 @@ import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapter; import com.github.kaktushose.jda.commands.dispatching.adapter.internal.TypeAdapters; import com.github.kaktushose.jda.commands.dispatching.expiration.ExpirationStrategy; -import com.github.kaktushose.jda.commands.dispatching.instantiation.Instantiator; -import com.github.kaktushose.jda.commands.dispatching.instantiation.spi.InstantiatorProvider; +import com.github.kaktushose.jda.commands.dispatching.instance.InstanceProvider; import com.github.kaktushose.jda.commands.dispatching.middleware.Middleware; import com.github.kaktushose.jda.commands.dispatching.middleware.Priority; import com.github.kaktushose.jda.commands.dispatching.middleware.internal.Middlewares; @@ -17,6 +16,7 @@ import com.github.kaktushose.jda.commands.dispatching.validation.internal.Validators; import com.github.kaktushose.jda.commands.embeds.error.DefaultErrorMessageFactory; import com.github.kaktushose.jda.commands.embeds.error.ErrorMessageFactory; +import com.github.kaktushose.jda.commands.extension.Extension; import com.github.kaktushose.jda.commands.internal.JDAContext; import com.github.kaktushose.jda.commands.permissions.DefaultPermissionsProvider; import com.github.kaktushose.jda.commands.permissions.PermissionsProvider; @@ -25,16 +25,29 @@ import net.dv8tion.jda.api.interactions.commands.localization.LocalizationFunction; import net.dv8tion.jda.api.interactions.commands.localization.ResourceBundleLocalizationFunction; import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.lang.annotation.Annotation; import java.util.*; +import java.util.function.Predicate; +import java.util.stream.Stream; /// This builder is used to build instances of [JDACommands]. /// /// Please note that values that can be set have a default implementation; -/// These default implementations are sometimes bases on reflections. If you want to avoid reflections, you have to provide your own implementations for: +/// These default implementations are sometimes based on reflections. If you want to avoid reflections, you have to provide your own implementations for: /// - [#descriptor(com.github.kaktushose.jda.commands.definitions.description.Descriptor)] /// - [#classFinders(ClassFinder...)] +/// - [#instanceProvider(InstanceProvider)] +/// +/// +/// In addition to manually configuring this builder, you can also provide implementations of [Extension] trough java's [`service +/// provider interface`][ServiceLoader], which are then applied by [#start()] or [#applyExtensions(FilterStrategy, String...)]. +/// +/// These implementations of [Extension] can be additionally configured by adding the according implementations of [Extension.Data] +/// by calling [#extensionData(Extension.Data...)]. (if supported by the extension) +/// /// /// ## Example /// ```java @@ -44,15 +57,15 @@ /// .classFinders(ClassFinder.reflective(Main.class), ClassFinders.explicit(ButtonInteraction.class)) /// .start(); /// ``` -/// +/// @see Extension public class JDACommandsBuilder { + private static final Logger log = LoggerFactory.getLogger(JDACommandsBuilder.class); private final Class baseClass; private final String[] packages; private final JDAContext context; private LocalizationFunction localizationFunction = ResourceBundleLocalizationFunction.empty().build(); - private Instantiator instantiator = null; - private InstantiatorProvider.Data instatiatorProviderData = null; + private InstanceProvider instanceProvider = null; private ExpirationStrategy expirationStrategy = ExpirationStrategy.AFTER_15_MINUTES; @@ -65,6 +78,9 @@ public class JDACommandsBuilder { private ReplyConfig globalReplyConfig = new ReplyConfig(); + private final Map, Extension.Data> extensionData = new HashMap<>(); + private boolean extensionsAlreadyApplied = false; + // registries private final Map, TypeAdapter> typeAdapters = new HashMap<>(); private final Set> middlewares = new HashSet<>(); @@ -105,14 +121,9 @@ public JDACommandsBuilder localizationFunction(@NotNull LocalizationFunction loc return this; } - /// @param instantiator The [Instantiator] to be used to instantiate interaction classes - public JDACommandsBuilder instantiator(Instantiator instantiator) { - this.instantiator = instantiator; - return this; - } - - public JDACommandsBuilder instantiatorProviderData(InstantiatorProvider.Data instatiatorProviderData) { - this.instatiatorProviderData = instatiatorProviderData; + /// @param instanceProvider the implementation of [InstanceProvider] to be used. + public JDACommandsBuilder instanceProvider(InstanceProvider instanceProvider) { + this.instanceProvider = instanceProvider; return this; } @@ -184,12 +195,68 @@ public JDACommandsBuilder globalReplyConfig(@NotNull ReplyConfig globalReplyConf return this; } - /// This method instantiates an instance of [JDACommands] and starts the framework. + /// Registers instances of implementations of [Extension.Data] to be used by the according implementation + /// of [Extension] to configure it properly. + /// + /// @param data the instances of [Extension.Data] to be used + public JDACommandsBuilder extensionData(Extension.Data... data) { + for (Extension.Data entity : data) { + extensionData.put(entity.getClass(), entity); + } + return this; + } + + /// After filtering the by SPI registered implementations of [Extension] according to the chosen [FilterStrategy], + /// this method loads all implementations of [Extension] by executing [Extension#configure(JDACommandsBuilder, Extension.Data)]. + /// + /// If this method is not called explicitly all [Extension]s will be loaded by [#start()]. + /// + /// This methods should only be used if you want to filter some implementations of [Extension] or if you want to override + /// some values set by any [Extension#configure(JDACommandsBuilder, Extension.Data)]. + /// + /// @apiNote This method compares the [`fully classified class name`][Class#getName()] of all [Extension] implementations by using [String#startsWith(String)], + /// so it's possible to include/exclude a bunch of classes in the same package by just providing the package name. + /// + /// @param strategy the filtering strategy to be used either [FilterStrategy#INCLUDE] or [FilterStrategy#EXCLUDE] + /// @param classes the classes to be filtered + @SuppressWarnings("unchecked") + public JDACommandsBuilder applyExtensions(FilterStrategy strategy, String... classes) { + ServiceLoader.load(Extension.class) + .stream() + .peek(provider -> log.debug("Found extension: {}", provider.type())) + .filter(provider -> { + Stream filterStream = Arrays.stream(classes); + Predicate startsWith = s -> provider.type().getName().startsWith(s); + + return switch (strategy) { + case INCLUDE -> filterStream.anyMatch(startsWith); + case EXCLUDE -> filterStream.noneMatch(startsWith); + }; + }) + .peek(provider -> log.debug("Using extension {}", provider.type())) + .map(ServiceLoader.Provider::get) + .forEach(extension -> extension.configure(this, extensionData.get(extension.dataType()))); + + extensionsAlreadyApplied = true; + return this; + } + + /// The two available filter strategies + public enum FilterStrategy { + /// includes the defined classes + INCLUDE, + /// excludes the defined classes + EXCLUDE + } + + /// This method applies all found implementations of [Extension] (if [#applyExtensions(FilterStrategy, String...)] isn't called explicitly), + /// instantiates an instance of [JDACommands] and starts the framework. @NotNull public JDACommands start() { - if (instantiator == null) { - instantiator = findDefaultInstantiator(); - } + // exclude no packages -> apply all + if (!extensionsAlreadyApplied) applyExtensions(FilterStrategy.EXCLUDE); + + validateConfiguration(); JDACommands jdaCommands = new JDACommands( context, @@ -199,21 +266,21 @@ public JDACommands start() { errorMessageFactory, guildScopeProvider, new InteractionRegistry(new Validators(this.validators), localizationFunction, descriptor), - instantiator, + instanceProvider, globalReplyConfig ); return jdaCommands.start(classFinders, baseClass, packages); } - private Instantiator findDefaultInstantiator() { - return ServiceLoader.load(InstantiatorProvider.class) - .stream() - .map(ServiceLoader.Provider::get) - .max(Comparator.comparingInt(InstantiatorProvider::priority)) - .orElseThrow(() -> new IllegalStateException( - "No InstantiatorProvider was found! Please use a default integration provided by jda-commands like the guice integration or write your own.") - ) - .create(instatiatorProviderData); + private void validateConfiguration() { + if (instanceProvider == null) throw new ConfigurationException("An implementation of com.github.kaktushose.jda.commands.dispatching.instantiation.InstanceProvider must be set!"); + } + + /// Will be thrown if anything goes wrong while configuring jda-commands. + public static class ConfigurationException extends RuntimeException { + public ConfigurationException(String message) { + super("Error while trying to configure jda-commands: " + message); + } } } diff --git a/jda-commands/src/main/java/com/github/kaktushose/jda/commands/annotations/Implementation.java b/jda-commands/src/main/java/com/github/kaktushose/jda/commands/annotations/Implementation.java index 5efb5eb7..94b73b19 100644 --- a/jda-commands/src/main/java/com/github/kaktushose/jda/commands/annotations/Implementation.java +++ b/jda-commands/src/main/java/com/github/kaktushose/jda/commands/annotations/Implementation.java @@ -13,7 +13,7 @@ /// Indicates that the annotated class is a custom implementation that should replace the default implementation. /// -/// @see "ImplementationRegistry -> TBD DI" +/// @see "ImplementationRegistry -> TBD DependencyInjection" /// @see Middleware /// @see Validator /// @see TypeAdapter diff --git a/jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/DispatchingContext.java b/jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/DispatchingContext.java similarity index 78% rename from jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/DispatchingContext.java rename to jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/DispatchingContext.java index 52006529..5631a513 100644 --- a/jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/DispatchingContext.java +++ b/jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/DispatchingContext.java @@ -1,10 +1,11 @@ -package com.github.kaktushose.jda.commands.dispatching.handling; +package com.github.kaktushose.jda.commands.dispatching; import com.github.kaktushose.jda.commands.definitions.interactions.InteractionDefinition; import com.github.kaktushose.jda.commands.definitions.interactions.InteractionRegistry; -import com.github.kaktushose.jda.commands.dispatching.instantiation.Instantiator; import com.github.kaktushose.jda.commands.dispatching.adapter.internal.TypeAdapters; import com.github.kaktushose.jda.commands.dispatching.expiration.ExpirationStrategy; +import com.github.kaktushose.jda.commands.dispatching.handling.EventHandler; +import com.github.kaktushose.jda.commands.dispatching.instance.InstanceProvider; import com.github.kaktushose.jda.commands.dispatching.middleware.internal.Middlewares; import com.github.kaktushose.jda.commands.embeds.error.ErrorMessageFactory; import org.jetbrains.annotations.ApiStatus; @@ -16,6 +17,6 @@ public record DispatchingContext(Middlewares middlewares, InteractionRegistry registry, TypeAdapters adapterRegistry, ExpirationStrategy expirationStrategy, - Instantiator instantiator, + InstanceProvider instanceProvider, InteractionDefinition.ReplyConfig globalReplyConfig) { } diff --git a/jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/JDAEventListener.java b/jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/JDAEventListener.java index 37248cd6..5caa9238 100644 --- a/jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/JDAEventListener.java +++ b/jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/JDAEventListener.java @@ -1,7 +1,6 @@ package com.github.kaktushose.jda.commands.dispatching; import com.github.kaktushose.jda.commands.definitions.interactions.CustomId; -import com.github.kaktushose.jda.commands.dispatching.handling.DispatchingContext; import net.dv8tion.jda.api.events.interaction.GenericInteractionCreateEvent; import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent; import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent; diff --git a/jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/Runtime.java b/jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/Runtime.java index b9870b94..cef17a75 100644 --- a/jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/Runtime.java +++ b/jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/Runtime.java @@ -1,13 +1,14 @@ package com.github.kaktushose.jda.commands.dispatching; -import com.github.kaktushose.jda.commands.definitions.interactions.InteractionDefinition; import com.github.kaktushose.jda.commands.dispatching.context.KeyValueStore; import com.github.kaktushose.jda.commands.dispatching.expiration.ExpirationStrategy; -import com.github.kaktushose.jda.commands.dispatching.handling.*; +import com.github.kaktushose.jda.commands.dispatching.handling.AutoCompleteHandler; +import com.github.kaktushose.jda.commands.dispatching.handling.ComponentHandler; +import com.github.kaktushose.jda.commands.dispatching.handling.EventHandler; +import com.github.kaktushose.jda.commands.dispatching.handling.ModalHandler; import com.github.kaktushose.jda.commands.dispatching.handling.command.ContextCommandHandler; import com.github.kaktushose.jda.commands.dispatching.handling.command.SlashCommandHandler; -import com.github.kaktushose.jda.commands.dispatching.instantiation.InstantiationContext; -import com.github.kaktushose.jda.commands.dispatching.instantiation.Instantiator; +import com.github.kaktushose.jda.commands.dispatching.instance.InstanceProvider; import net.dv8tion.jda.api.events.interaction.GenericInteractionCreateEvent; import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent; import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent; @@ -21,8 +22,6 @@ import java.io.Closeable; import java.time.LocalDateTime; -import java.util.HashMap; -import java.util.Map; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; @@ -48,17 +47,15 @@ public final class Runtime implements Closeable { private final ContextCommandHandler contextCommandHandler; private final ComponentHandler componentHandler; private final String id; - private final Map, Object> instances; private final BlockingQueue blockingQueue; private final Thread executionThread; private final KeyValueStore keyValueStore = new KeyValueStore(); private final ModalHandler modalHandler; - private final Instantiator instantiator; + private final InstanceProvider instanceProvider; private LocalDateTime lastActivity = LocalDateTime.now(); private Runtime(@NotNull String id, @NotNull DispatchingContext dispatchingContext) { this.id = id; - this.instances = new HashMap<>(); expirationStrategy = dispatchingContext.expirationStrategy(); blockingQueue = new LinkedBlockingQueue<>(); slashCommandHandler = new SlashCommandHandler(dispatchingContext); @@ -67,7 +64,7 @@ private Runtime(@NotNull String id, @NotNull DispatchingContext dispatchingConte componentHandler = new ComponentHandler(dispatchingContext); modalHandler = new ModalHandler(dispatchingContext); - this.instantiator = dispatchingContext.instantiator().forRuntime(id); + this.instanceProvider = dispatchingContext.instanceProvider().forRuntime(id); this.executionThread = Thread.ofVirtual() .name("JDAC Runtime-Thread %s".formatted(id)) @@ -126,16 +123,8 @@ public KeyValueStore keyValueStore() { } @SuppressWarnings("unchecked") - public T interactionClass(Class clazz) { - return (T) instances.computeIfAbsent(clazz, _ -> instantiator.instantiate(clazz, instantiationContext())); - } - - private InstantiationContext instantiationContext() { - return new InstantiationContext(this); - } - - public Object instance(InteractionDefinition definition) { - return interactionClass(definition.classDescription().clazz()); + public T interactionInstance(Class clazz) { + return instanceProvider.instance(clazz, new InstanceProvider.Context(this)); } @Override diff --git a/jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/events/Event.java b/jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/events/Event.java index 91eb0f99..122c2451 100644 --- a/jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/events/Event.java +++ b/jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/events/Event.java @@ -92,10 +92,11 @@ public KeyValueStore kv() { /// Returns an instance of a class annotated with [`Interaction`][com.github.kaktushose.jda.commands.annotations.interactions.Interaction], /// that is bound to the underlying [`Runtime`]({@docRoot}/index.html#runtime-concept-heading). + /// /// @return the interaction class instance @Nullable - public I interactionClass(Class interactionClass) { - return runtime.interactionClass(interactionClass); + public I interactionInstance(Class interactionClass) { + return runtime.interactionInstance(interactionClass); } @Override diff --git a/jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/AutoCompleteHandler.java b/jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/AutoCompleteHandler.java index df1fc406..dc6886e1 100644 --- a/jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/AutoCompleteHandler.java +++ b/jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/AutoCompleteHandler.java @@ -2,6 +2,7 @@ import com.github.kaktushose.jda.commands.definitions.interactions.AutoCompleteDefinition; import com.github.kaktushose.jda.commands.definitions.interactions.command.SlashCommandDefinition; +import com.github.kaktushose.jda.commands.dispatching.DispatchingContext; import com.github.kaktushose.jda.commands.dispatching.Runtime; import com.github.kaktushose.jda.commands.dispatching.context.InvocationContext; import com.github.kaktushose.jda.commands.dispatching.events.interactions.AutoCompleteEvent; diff --git a/jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/ComponentHandler.java b/jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/ComponentHandler.java index c6ecb8d6..5d5c4757 100644 --- a/jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/ComponentHandler.java +++ b/jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/ComponentHandler.java @@ -2,6 +2,7 @@ import com.github.kaktushose.jda.commands.definitions.interactions.CustomId; import com.github.kaktushose.jda.commands.definitions.interactions.component.ComponentDefinition; +import com.github.kaktushose.jda.commands.dispatching.DispatchingContext; import com.github.kaktushose.jda.commands.dispatching.Runtime; import com.github.kaktushose.jda.commands.dispatching.context.InvocationContext; import com.github.kaktushose.jda.commands.dispatching.events.interactions.ComponentEvent; diff --git a/jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/EventHandler.java b/jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/EventHandler.java index 1bfc5797..f9ba18ec 100644 --- a/jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/EventHandler.java +++ b/jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/EventHandler.java @@ -2,6 +2,7 @@ import com.github.kaktushose.jda.commands.definitions.interactions.InteractionDefinition; import com.github.kaktushose.jda.commands.definitions.interactions.InteractionRegistry; +import com.github.kaktushose.jda.commands.dispatching.DispatchingContext; import com.github.kaktushose.jda.commands.dispatching.Runtime; import com.github.kaktushose.jda.commands.dispatching.adapter.internal.TypeAdapters; import com.github.kaktushose.jda.commands.dispatching.context.InvocationContext; @@ -98,7 +99,7 @@ private void invoke(@NotNull InvocationContext invocation, @NotNull Runtime r definition.methodDescription().name(), arguments ); - Object instance = runtime.instance(definition); + Object instance = runtime.interactionInstance(definition.classDescription().clazz()); INVOCATION_PERMITTED.set(true); definition.invoke(instance, invocation); diff --git a/jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/ModalHandler.java b/jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/ModalHandler.java index 15e5f6be..b0ca610b 100644 --- a/jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/ModalHandler.java +++ b/jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/ModalHandler.java @@ -2,6 +2,7 @@ import com.github.kaktushose.jda.commands.definitions.interactions.CustomId; import com.github.kaktushose.jda.commands.definitions.interactions.ModalDefinition; +import com.github.kaktushose.jda.commands.dispatching.DispatchingContext; import com.github.kaktushose.jda.commands.dispatching.Runtime; import com.github.kaktushose.jda.commands.dispatching.context.InvocationContext; import com.github.kaktushose.jda.commands.dispatching.events.interactions.ModalEvent; diff --git a/jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/command/ContextCommandHandler.java b/jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/command/ContextCommandHandler.java index fbe2cdd8..3ab56d04 100644 --- a/jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/command/ContextCommandHandler.java +++ b/jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/command/ContextCommandHandler.java @@ -5,7 +5,7 @@ import com.github.kaktushose.jda.commands.dispatching.Runtime; import com.github.kaktushose.jda.commands.dispatching.context.InvocationContext; import com.github.kaktushose.jda.commands.dispatching.events.interactions.CommandEvent; -import com.github.kaktushose.jda.commands.dispatching.handling.DispatchingContext; +import com.github.kaktushose.jda.commands.dispatching.DispatchingContext; import com.github.kaktushose.jda.commands.dispatching.handling.EventHandler; import net.dv8tion.jda.api.events.interaction.command.GenericContextInteractionEvent; import org.jetbrains.annotations.ApiStatus; diff --git a/jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/command/SlashCommandHandler.java b/jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/command/SlashCommandHandler.java index 67267a68..0c7a726e 100644 --- a/jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/command/SlashCommandHandler.java +++ b/jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/command/SlashCommandHandler.java @@ -5,7 +5,7 @@ import com.github.kaktushose.jda.commands.dispatching.adapter.internal.TypeAdapters; import com.github.kaktushose.jda.commands.dispatching.context.InvocationContext; import com.github.kaktushose.jda.commands.dispatching.events.interactions.CommandEvent; -import com.github.kaktushose.jda.commands.dispatching.handling.DispatchingContext; +import com.github.kaktushose.jda.commands.dispatching.DispatchingContext; import com.github.kaktushose.jda.commands.dispatching.handling.EventHandler; import com.github.kaktushose.jda.commands.dispatching.reply.MessageReply; import com.github.kaktushose.jda.commands.internal.Helpers; diff --git a/jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/instance/InstanceProvider.java b/jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/instance/InstanceProvider.java new file mode 100644 index 00000000..8674de67 --- /dev/null +++ b/jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/instance/InstanceProvider.java @@ -0,0 +1,43 @@ +package com.github.kaktushose.jda.commands.dispatching.instance; + +import com.github.kaktushose.jda.commands.annotations.interactions.Interaction; +import com.github.kaktushose.jda.commands.dispatching.Runtime; + +/// An [InstanceProvider] is used get instances of classes annotated with [Interaction], if needed creating those. +/// +/// At the time of [`Runtime`]({@docRoot}/index.html#runtime-concept-heading) creation [#forRuntime(String)] is called, +/// allowing the InstanceProvider to provide an instance that hasn't to be thread safe and is bound to one Runtime. +/// +/// Please also note that per [`Runtime`]({@docRoot}/index.html#runtime-concept-heading) there can be multiple +/// classes annotated with [Interaction] but there can be only one instance per class of those per [`Runtime`]({@docRoot}/index.html#runtime-concept-heading). +/// Instances of interactions should be treated like runtime scoped singletons, so to speak. +@FunctionalInterface +public interface InstanceProvider { + /// This method will be called each time an instance of a specific class is needed. + /// Please note the points specified in the class docs. + /// + /// @param clazz the [Class] of needed instance + /// @param context a context that gives additional useful information or provide some needed functionality + T instance(Class clazz, Context context); + + /// Called each time a [`Runtime`]({@docRoot}/index.html#runtime-concept-heading) creation [#forRuntime(String)] is created. + /// + /// @param id the runtime id + /// @return a specific instance of [InstanceProvider] belonging to provided runtime. + default InstanceProvider forRuntime(String id) { + return this; + } + + class Context { + private final Runtime runtime; + + public Context(Runtime runtime) { + this.runtime = runtime; + } + + /// the runtime id + public String runtimeId() { + return runtime.id(); + } + } +} diff --git a/jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/instance/package-info.java b/jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/instance/package-info.java new file mode 100644 index 00000000..35237456 --- /dev/null +++ b/jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/instance/package-info.java @@ -0,0 +1,2 @@ +/// This package contains logic needed to get instances of classes annotated with [com.github.kaktushose.jda.commands.annotations.interactions.Interaction] +package com.github.kaktushose.jda.commands.dispatching.instance; \ No newline at end of file diff --git a/jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/instantiation/InstantiationContext.java b/jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/instantiation/InstantiationContext.java deleted file mode 100644 index 084dabe2..00000000 --- a/jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/instantiation/InstantiationContext.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.github.kaktushose.jda.commands.dispatching.instantiation; - -import com.github.kaktushose.jda.commands.dispatching.Runtime; - -public class InstantiationContext { - private final com.github.kaktushose.jda.commands.dispatching.Runtime runtime; - - public InstantiationContext(Runtime runtime) { - this.runtime = runtime; - } - - public T interactionClass(Class clazz) { - return runtime.interactionClass(clazz); - } -} diff --git a/jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/instantiation/Instantiator.java b/jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/instantiation/Instantiator.java deleted file mode 100644 index 0d39f794..00000000 --- a/jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/instantiation/Instantiator.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.github.kaktushose.jda.commands.dispatching.instantiation; - -@FunctionalInterface -public interface Instantiator { - T instantiate(Class clazz, InstantiationContext context); - - default Instantiator forRuntime(String id) { - return this; - } -} diff --git a/jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/instantiation/spi/InstantiatorProvider.java b/jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/instantiation/spi/InstantiatorProvider.java deleted file mode 100644 index fe7fbabf..00000000 --- a/jda-commands/src/main/java/com/github/kaktushose/jda/commands/dispatching/instantiation/spi/InstantiatorProvider.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.github.kaktushose.jda.commands.dispatching.instantiation.spi; - -import com.github.kaktushose.jda.commands.dispatching.instantiation.Instantiator; - -public interface InstantiatorProvider { - Instantiator create(Data data); - int priority(); - - interface Data {} -} diff --git a/jda-commands/src/main/java/com/github/kaktushose/jda/commands/extension/Extension.java b/jda-commands/src/main/java/com/github/kaktushose/jda/commands/extension/Extension.java new file mode 100644 index 00000000..4da69c67 --- /dev/null +++ b/jda-commands/src/main/java/com/github/kaktushose/jda/commands/extension/Extension.java @@ -0,0 +1,40 @@ +package com.github.kaktushose.jda.commands.extension; + +import com.github.kaktushose.jda.commands.JDACommandsBuilder; +import com.github.kaktushose.jda.commands.definitions.description.Descriptor; +import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapter; +import com.github.kaktushose.jda.commands.dispatching.instance.InstanceProvider; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/// Implementations of this interface, that are registered by java's service provider interface, will be called +/// in [JDACommandsBuilder] to configure the framework. +/// +/// Extensions can for example set an implementation of [InstanceProvider] or [Descriptor] or +/// register additional implementations of [TypeAdapter]. +/// +/// If the implementation of this class needs additional configuration, implementations have to provide an +/// own implementation of [Data] that the user have to register in the builder before the extensions are loaded. +public interface Extension { + /// Will be called by the [JDACommandsBuilder] at a specific time to let the specific implementation + /// configure the framework. + /// + /// Please note that extensions shouldn't override user specific options and are mainly + /// intended to register things like [InstanceProvider], [Descriptor] or additional [TypeAdapter]s + /// + /// For further information please take a look at [JDACommandsBuilder]. + /// + /// @param builder the used instance of [JDACommandsBuilder] + /// @see JDACommandsBuilder#applyExtensions(JDACommandsBuilder.FilterStrategy, String...) + void configure(@NotNull JDACommandsBuilder builder, @Nullable T data); + + /// @return the [Class] of the custom [Data] implementation + @NotNull + default Class dataType() { + return null; + } + + /// Implementations of this interface are providing additional configuration to implementations of [Extension] + interface Data {} + +} diff --git a/jda-commands/src/main/java/com/github/kaktushose/jda/commands/extension/package-info.java b/jda-commands/src/main/java/com/github/kaktushose/jda/commands/extension/package-info.java new file mode 100644 index 00000000..7ee14546 --- /dev/null +++ b/jda-commands/src/main/java/com/github/kaktushose/jda/commands/extension/package-info.java @@ -0,0 +1,5 @@ +/// This package provides an additional way to configure jda-commands by providing implementations of [com.github.kaktushose.jda.commands.extension.Extension] trough java's +/// service provider interface. +/// +/// @see java.util.ServiceLoader +package com.github.kaktushose.jda.commands.extension; \ No newline at end of file diff --git a/jda-commands/src/main/java/module-info.java b/jda-commands/src/main/java/module-info.java index cf178b1c..7c403469 100644 --- a/jda-commands/src/main/java/module-info.java +++ b/jda-commands/src/main/java/module-info.java @@ -8,6 +8,7 @@ requires transitive org.jetbrains.annotations; requires transitive jakarta.inject; + requires java.naming; // base package exports com.github.kaktushose.jda.commands; @@ -42,8 +43,7 @@ exports com.github.kaktushose.jda.commands.dispatching.validation; - exports com.github.kaktushose.jda.commands.dispatching.instantiation; - exports com.github.kaktushose.jda.commands.dispatching.instantiation.spi; + exports com.github.kaktushose.jda.commands.dispatching.instance; // embed exports com.github.kaktushose.jda.commands.embeds; @@ -55,5 +55,8 @@ // command scope api exports com.github.kaktushose.jda.commands.scope; - uses com.github.kaktushose.jda.commands.dispatching.instantiation.spi.InstantiatorProvider; + // extensions + exports com.github.kaktushose.jda.commands.extension; + + uses com.github.kaktushose.jda.commands.extension.Extension; } \ No newline at end of file diff --git a/test/src/main/java/module-info.java b/test/src/main/java/module-info.java index 7c785729..42a28713 100644 --- a/test/src/main/java/module-info.java +++ b/test/src/main/java/module-info.java @@ -2,5 +2,4 @@ requires net.dv8tion.jda; requires org.slf4j; requires jda.commands.guice; - requires jda.commands; } \ No newline at end of file