Writing an Extension¶
Entrypoint¶
The entrypoint of the Extension API is the so called
Extension
interface, which your extensions "entry class" must implement:
Extension.Data
¶
Furthermore, each entry class must override the Extension#init(T data)
method,
which will be called when JDA-Commands loads the extension. It can be used to configure extension specific options with help of
an own implementation of
Extension.Data
.
public class MyExtension implements Extension<Void> {
@Override
public void init(Void data) {
// doing nothing if not needed
}
}
public class MyExtension implements Extension<MyExtensionData> {
@Override
public void init(MyExtensionData data) { //(1)!
if (data != null) {
doSomeConfig(data.someOption());
}
}
@Override
public @NotNull Class<MyExtensionData> dataType() {
return MyExtensionData.class;
}
}
public record MyExtensionData(String someOption) implements Extension.Data {}
- If no instance of
MyExtensionData
is passed by the user, this argument will be setnull
.
Providing Implementations¶
Currently, extensions support to provide custom implementations of any class extending
ExtensionProvideable
,
that is:
ClassFinder
Descriptor
InteractionControllerInstantiator
ErrorMessageFactory
Implementation.MiddlewareContainer
(wrapper type forMiddleware
)Implementation.TypeAdapterContainer
(wrapper type forTypeAdapter
)Implementation.ValidatorContainer
(wrapper type forValidator
)PermissionsProvider
GuildScopeProvider
To provide custom implementations we have to implement the Extensions#providedImplementations()
method.
This method returns a collection of all implementations that an extension provides, wrapped in an instance of
Implementation
.
public class MyExtension implements Extension<MyExtensionData> {
@Override
public void init(MyExtensionData data) {
if (data != null) {
doSomeConfig(data.someOption());
}
}
@Override
public @NotNull Collection<Implementation<?>> providedImplementations() {
return List.of(new Implementation.single(
Descriptor.class,
_ -> new MyCustomDescriptor(this))
);
}
@Override
public @NotNull Class<MyExtensionData> dataType() {
return MyExtensionData.class;
}
}
public record MyExtensionData(String someOption) implements Extension.Data {}
Identity Equality¶
Warning
providedImplementations()
and the supplier of Implementation
will be called
multiple times during JDA-Commands startup!
Thus, providedImplementations()
must always return the same enumeration of Implementation
s. If the identity
equality is important, the supplier of Implementation
must also always return the same instances.
JDA-Commands doesn't rely on identity equality, but you might have fields that store information in that class. It should, of course, be the same instance then each time.
MyExtension
could look something like this in that case:
public class MyExtension implements Extension<MyExtensionData> {
private MyCustomDescriptor descriptor;
@Override
public void init(MyExtensionData data) {
if (data != null) {
doSomeConfig(data.someOption());
descriptor = new MyCustomDescriptor(this);
}
}
@Override
public @NotNull Collection<Implementation<?>> providedImplementations() {
return List.of(new Implementation.single(
Descriptor.class,
_ -> descriptor)
);
}
@Override
public @NotNull Class<MyExtensionData> dataType() {
return MyExtensionData.class;
}
}
public record MyExtensionData(String someOption) implements Extension.Data {}
The Implementation
class¶
The Implementation
class has 2 main purposes: State which class the custom implementation is for and providing instance(s) of those custom implementations.
To provide a custom implementation you have to create an instance of Implementation
and provide
- the
type
of this Extension (that is a class/interface extending ExtensionProvidable) - a
supplier
in form ofjava.util.fuction.Function
that takesJDACBuilderData
as an argument and returns a list of instances of custom implementations for the specific type
new Implemenation(
ClassFinder.class,
builderData -> List.of(new CustomClassFinderOne(), new CustomClassFinderSecond(builderData.descriptor()))
)
It's also important that only the following types support multiple instances:
ClassFinder
Implementation.MiddlewareContainer
(wrapper type for Middleware)Implementation.TypeAdapterContainer
(wrapper type for TypeAdapter)Implementation.ValidatorContainer
(wrapper type for Validator)
For all other types
single(Class<T>,Function<JDACBuilderData,T> supplier)
should be used.
The provided instance of JDACBuilderData
only supports read access to the builder, which can be used to obtain any other
part of the framework as a dependency. It's important to have in mind, that calls to this object will check all registered
extensions for the needed implementation, thus cycling dependencies will result in an exception.
Tip
If the list returned by supplier is empty, this implementation will be treated as non-existent, which is useful for dynamic registration of custom implementation.
Registration¶
Custom extensions are found with help of Javas ServiceLoader API.
To register the above MyExtension
we have to create a file in our resources\META-INF
directory called
com.github.kaktushose.jda.commands.extension.Extension
.
The full class name of our class MyExtension
(e.g. my.package.MyExtension
) must be the content of this file.
The extension can now be found and loaded by JDA-Commands.