Skip to content

Commit

Permalink
more improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
Kaktushose committed Feb 9, 2025
1 parent c52c027 commit 238da28
Show file tree
Hide file tree
Showing 12 changed files with 136 additions and 144 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,23 @@
import java.util.SequencedCollection;

/// [ClassFinder]s search for classes annotated with a specific annotation
public non-sealed interface ClassFinder extends Implementation.ExtensionImplementable {
public non-sealed interface ClassFinder extends Implementation.ExtensionProvidable {

/// This method searches for classes annotated with the given annotation.
///
/// @param annotationClass the class of the annotation
/// @return the found classes
@NotNull
SequencedCollection<Class<?>> search(Class<? extends Annotation> annotationClass);
SequencedCollection<Class<?>> search(@NotNull Class<? extends Annotation> annotationClass);

/// This method searches for classes annotated with the given annotation, which have the given super type.
///
/// @param annotationClass the class of the annotation
/// @param superType the [Class], which is a supertype of the found classes
/// @return the found classes
@SuppressWarnings("unchecked")
default <T> SequencedCollection<Class<T>> search(Class<? extends Annotation> annotationClass, Class<T> superType) {
@NotNull
default <T> SequencedCollection<Class<T>> search(@NotNull Class<? extends Annotation> annotationClass, @NotNull Class<T> superType) {
return search(annotationClass).stream()
.filter(superType::isAssignableFrom)
.map(aClass -> (Class<T>) aClass)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

/// A [Descriptor] takes a [Class] as input and transforms it into a [ClassDescription].
@FunctionalInterface
public non-sealed interface Descriptor extends Implementation.ExtensionImplementable {
public non-sealed interface Descriptor extends Implementation.ExtensionProvidable {

/// the default [Descriptor], which builds [ClassDescription] using [java.lang.reflect]
Descriptor REFLECTIVE = new ReflectiveDescriptor();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public ReflectiveClassFinder(Class<?> clazz, String[] packages) {
}

@Override
public @NotNull SequencedCollection<Class<?>> search(Class<? extends Annotation> annotationClass) {
public @NotNull SequencedCollection<Class<?>> search(@NotNull Class<? extends Annotation> annotationClass) {
var filter = new FilterBuilder();
for (String pkg : packages) {
filter.includePackage(pkg);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
/// 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 non-sealed interface InteractionControllerInstantiator extends Implementation.ExtensionImplementable {
public non-sealed interface InteractionControllerInstantiator extends Implementation.ExtensionProvidable {

/// This method will be called each time an instance of a class annotated with [Interaction] is needed.
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import com.github.kaktushose.jda.commands.definitions.interactions.InteractionDefinition;
import com.github.kaktushose.jda.commands.definitions.interactions.command.OptionDataDefinition.ConstraintDefinition;
import com.github.kaktushose.jda.commands.extension.Implementation.ExtensionImplementable;
import com.github.kaktushose.jda.commands.extension.Implementation.ExtensionProvidable;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.events.interaction.GenericInteractionCreateEvent;
import net.dv8tion.jda.api.utils.messages.MessageCreateData;
Expand All @@ -15,7 +15,7 @@
///
/// @see DefaultErrorMessageFactory
/// @see JsonErrorMessageFactory
public non-sealed interface ErrorMessageFactory extends ExtensionImplementable {
public non-sealed interface ErrorMessageFactory extends ExtensionProvidable {

/// Gets a [MessageCreateData] to send when type adapting of the user input failed.
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import com.github.kaktushose.jda.commands.JDACBuilder;
import com.github.kaktushose.jda.commands.definitions.description.Descriptor;
import com.github.kaktushose.jda.commands.dispatching.instance.InteractionControllerInstantiator;
import com.github.kaktushose.jda.commands.extension.Implementation.ExtensionImplementable;
import com.github.kaktushose.jda.commands.extension.Implementation.ExtensionProvidable;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

Expand All @@ -13,12 +13,12 @@
/// Implementations of this interface, that are registered by Javas service provider interface, will be called
/// in [JDACBuilder] to configure the framework.
///
/// This class provides ways to extend the framework with own functionality:
/// - [#providedImplementations()]: By implementing this method and returning a list of own implementations of
/// interfaces marked with [ExtensionImplementable], you can for example provide an own implementation of [InteractionControllerInstantiator]
/// or [Descriptor]. These implementations will override the default ones.
/// This interface provides ways to extend the framework with own functionality:
/// - [#providedImplementations()]: By implementing this method and returning a collection of [Implementation]s, you can
/// for example provide an own implementation of [InteractionControllerInstantiator] or [Descriptor]. These
/// implementations will override the default ones.
///
/// If the implementation of this class needs additional configuration data, implementations have to provide an
/// - If the [Extension] needs additional configuration data, implementations have to provide an
/// own implementation of [Data] that the user has to register in the builder by calling [JDACBuilder#extensionData(Data...)].
///
/// ### Example
Expand Down Expand Up @@ -53,17 +53,15 @@
/// ```
public interface Extension {

/// Will be called right after jda-commands loaded the Extension.
/// Initialises the [Extension] with to provided [Data]. Will be called right after jda-commands loaded the Extension.
///
/// @param data The custom implementation of [Data] if given by the User. This can be safely cast to the type returned by [#dataType()].
void init(@Nullable Data data);

/// By implementing this method and returning a list of own implementations of interfaces marked with
/// [ExtensionImplementable], you can for example provide an own implementation of [InteractionClassProvider]
/// or [Descriptor]. These implementations will override the default ones.
/// Gets a collection of [Implementation]s this [Extension] provides.
///
/// @return a collection of [Implementation]s that should be used to retrieve certain implementations of an interface.
/// @apiNote Please note that this method is called multiple times during framework creation. If the identity of the implementations
/// @return a collection of [Implementation]s
/// @implNote Please note that this method is called multiple times during framework creation. If the identity of the implementations
/// is important, you should always return the same instance.
@NotNull
default Collection<@NotNull Implementation<?>> providedImplementations() {
Expand All @@ -76,7 +74,7 @@ default Class<?> dataType() {
return Void.class;
}

/// Implementations of this interface are providing additional configuration to implementations of [Extension]
/// Implementations of this interface are providing additional configuration to implementations of [Extension].
interface Data {}

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,98 +14,94 @@
import org.jetbrains.annotations.NotNull;

import java.lang.annotation.Annotation;
import java.util.*;
import java.util.List;
import java.util.Map;
import java.util.SequencedCollection;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/// Instances of this class are used to provide custom implementations of classes implementing [ExtensionImplementable],
/// please note that [TypeAdapter]s, [Middleware]s and [Validator]s are only providable by their corresponding container types:
/// [TypeAdapterContainer], [MiddlewareContainer], [ValidatorContainer].
/// Instances of [Implementation] are used to provide custom implementations of [ExtensionProvidable] interfaces, namely:
/// - [ClassFinder]
/// - [Descriptor]
/// - [InteractionControllerInstantiator]
/// - [ErrorMessageFactory]
/// - [MiddlewareContainer] (wrapper type for [Middleware])
/// - [TypeAdapterContainer] (wrapper type for [TypeAdapter])
/// - [ValidatorContainer] (wrapper type for [Validator])
/// - [PermissionsProvider]
/// - [GuildScopeProvider]
///
/// Such instances are returned by [Extension#providedImplementations()] and used by the [JDACBuilder].
/// Such instances of [Implementation] are returned by [Extension#providedImplementations()] and used by the [JDACBuilder] to
/// configure jda-commands.
///
/// If the collection returned by [java.util.function.Supplier#get()] ([#supplier()]) is empty then this implementation is discarded and thus treated as non-existent.
/// @param type the [Class] of the implemented interface
/// @param supplier the [Function] used to retrieve instances of the custom implementation
/// @implNote If the [#supplier()] returns an empty collection, then this [Implementation] is discarded and thus treated as non-existent.

This comment has been minimized.

Copy link
@Goldmensch

Goldmensch Feb 10, 2025

Collaborator

This shouldn’t be a implNote, because it’s very relevant for designing Extensions

///
/// All extensions/ implementations can only provide one instance of an [ExtensionImplementable] interface, expect for
/// [TypeAdapter]s, [Middleware]s and [Validator]s, which can be implemented multiple times.
///
/// @param type the [Class] of the implemented interface
/// @param supplier the [java.util.function.Supplier] used to retrieve instances of the custom implementations
public record Implementation<T extends Implementation.ExtensionImplementable>(
/// @apiNote The [TypeAdapter]s, [Middleware]s and [Validator]s are only providable by their corresponding container types:

This comment has been minimized.

Copy link
@Goldmensch

Goldmensch Feb 10, 2025

Collaborator

Already said above in the enumeration

/// [TypeAdapterContainer], [MiddlewareContainer] and [ValidatorContainer].
/// @see Extension
public record Implementation<T extends Implementation.ExtensionProvidable>(
@NotNull Class<T> type,
@NotNull Function<@NotNull JDACBuilderData, @NotNull SequencedCollection<@NotNull T>> supplier
) {

/// A marker interface that all types providable by an [Implementation] implement
public sealed interface ExtensionImplementable permits ClassFinder, Descriptor, InteractionControllerInstantiator, ErrorMessageFactory, MiddlewareContainer, TypeAdapterContainer, ValidatorContainer, PermissionsProvider, GuildScopeProvider {}
/// A marker interface that all types providable by an [Extension] share.
public sealed interface ExtensionProvidable permits ClassFinder, Descriptor, InteractionControllerInstantiator, ErrorMessageFactory,
MiddlewareContainer, TypeAdapterContainer, ValidatorContainer, PermissionsProvider, GuildScopeProvider {}

/// A container type for providing [TypeAdapter]s.
/// @param type the [Class] for which the [TypeAdapter] should be registered
///
/// @param type the [Class] for which the [TypeAdapter] should be registered
/// @param adapter the [TypeAdapter] implementation
public record TypeAdapterContainer(@NotNull Class<?> type, @NotNull TypeAdapter<?> adapter) implements ExtensionImplementable {}
public record TypeAdapterContainer(@NotNull Class<?> type, @NotNull TypeAdapter<?> adapter) implements ExtensionProvidable {}

/// A container type for providing [Middleware]s.
/// @param priority the [Priority] with which the [Middleware] should be registered
///
/// @param priority the [Priority] with which the [Middleware] should be registered
/// @param middleware the [Middleware] implementation
public record MiddlewareContainer(@NotNull Priority priority, @NotNull Middleware middleware) implements ExtensionImplementable {}
public record MiddlewareContainer(@NotNull Priority priority, @NotNull Middleware middleware) implements ExtensionProvidable {}

/// A container type for providing [Validator]s.
///
/// @param annotation the [Annotation] for which the [Validator] should be registered
/// @param validator the [Validator] implementation
public record ValidatorContainer(@NotNull Class<? extends Annotation> annotation, @NotNull Validator validator) implements ExtensionImplementable {}

public static <T extends Implementation.ExtensionImplementable> Implementation<T> single(@NotNull Class<T> type, @NotNull Function<@NotNull JDACBuilderData, @NotNull T> supplier) {
return new Implementation<>(
type,
(builder -> List.of(supplier.apply(builder)))
);
/// @param validator the [Validator] implementation
public record ValidatorContainer(@NotNull Class<? extends Annotation> annotation, @NotNull Validator validator) implements ExtensionProvidable {}

public static <T extends ExtensionProvidable> Implementation<T> single(@NotNull Class<T> type,
@NotNull Function<@NotNull JDACBuilderData,
@NotNull T> supplier) {
return new Implementation<>(type, (builder -> List.of(supplier.apply(builder))));
}

SequencedCollection<T> implementations(JDACBuilderData builder) {
checkCycling(builder);
SequencedCollection<T> implementations(JDACBuilderData data) {
if (data.alreadyCalled.stream().anyMatch(provider -> provider.type.equals(type))) {
throw new JDACBuilder.ConfigurationException(
"Cycling dependencies while getting implementations of %s! \n%s".formatted(type, format(data))
);
}

builder.alreadyCalled.add(this); // scope entry
data.alreadyCalled.add(this); // scope entry

// other suppliers could be called here
// Scoping this will create a simple stack of already called methods, allowing checking for cycling dependencies (Implementation#type())
SequencedCollection<T> apply = supplier().apply(builder);
SequencedCollection<T> apply = supplier().apply(data);

builder.alreadyCalled.remove(this); // scope leave
data.alreadyCalled.remove(this); // scope leave
return apply;
}

private void checkCycling(JDACBuilderData builder) {
boolean alreadyCalled = builder.alreadyCalled
.stream()
.anyMatch(provider -> provider.type.equals(type));

if (alreadyCalled) {
List<GraphEntry> stack = builder.alreadyCalled
.reversed()
.stream()
.map(provider -> {
var extension = builder.implementation(provider.type)
.stream()
.findAny()
.map(Map.Entry::getKey)
.orElseThrow()
.getClass();
return new GraphEntry(extension, provider.type);
})
.toList();

throw new JDACBuilder.ConfigurationException("Cycling dependencies while getting implementations of %s! \n%s"
.formatted(type, format(stack)));
}
}
private record GraphEntry(Class<?> extension, Class<?> provides) {}

private record GraphEntry(
Class<?> extension,
Class<?> provides
) {}
private String format(JDACBuilderData data) {
List<GraphEntry> stack = data.alreadyCalled.reversed().stream()
.map(provider -> {
var extension = data.implementations(provider.type).stream().findAny().map(Map.Entry::getKey).orElseThrow().getClass();
return new GraphEntry(extension, provider.type);
})
.toList();

private String format(List<GraphEntry> stack) {
if (stack.size() == 1) {
GraphEntry entry = stack.getFirst();
return "%s provides and needs %s, thus calls itself".formatted(entry.extension.getSimpleName(), entry.provides.getSimpleName());
Expand Down
Loading

0 comments on commit 238da28

Please # to comment.