io.github.freya022.botcommands.internal.parameters.ResolverContainer.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of BotCommands Show documentation
Show all versions of BotCommands Show documentation
A Kotlin-first (and Java) framework that makes creating Discord bots a piece of cake, using the JDA library.
The newest version!
package io.github.freya022.botcommands.internal.parameters
import io.github.freya022.botcommands.api.core.reflect.ParameterWrapper
import io.github.freya022.botcommands.api.core.reflect.throwUser
import io.github.freya022.botcommands.api.core.service.ServiceContainer
import io.github.freya022.botcommands.api.core.service.annotations.BService
import io.github.freya022.botcommands.api.core.service.annotations.Resolver
import io.github.freya022.botcommands.api.core.service.findAnnotationOnService
import io.github.freya022.botcommands.api.core.service.getServiceNamesForAnnotation
import io.github.freya022.botcommands.api.core.utils.*
import io.github.freya022.botcommands.api.parameters.*
import io.github.freya022.botcommands.api.parameters.resolvers.*
import io.github.freya022.botcommands.internal.utils.annotationRef
import io.github.freya022.botcommands.internal.utils.throwInternal
import io.github.oshai.kotlinlogging.KotlinLogging
import kotlin.collections.set
import kotlin.reflect.KClass
private val logger = KotlinLogging.logger { }
private val compatibleInterfaces = listOf(
TextParameterResolver::class,
QuotableTextParameterResolver::class,
SlashParameterResolver::class,
ComponentParameterResolver::class,
UserContextParameterResolver::class,
MessageContextParameterResolver::class,
ModalParameterResolver::class,
TimeoutParameterResolver::class,
ICustomResolver::class
)
@BService
internal class ResolverContainer internal constructor(
serviceContainer: ServiceContainer,
resolverFactories: List>,
) {
private data class CacheKey(
private val requestedType: KClass>,
private val resolverRequest: ResolverRequest
)
private val factories: MutableList> = arrayOfSize(50)
private val cache: MutableMap?> = hashMapOf()
init {
fun addResolver(resolver: ParameterResolver<*, *>, annotation: Resolver) {
fun ParameterResolver<*, *>.hasCompatibleInterface(): Boolean {
return compatibleInterfaces.any { it.isInstance(this) }
}
require(resolver.hasCompatibleInterface()) {
"The resolver should implement at least one of these interfaces: ${compatibleInterfaces.joinToString { it.simpleName!! }}"
}
factories += when (resolver) {
is ClassParameterResolver -> resolver.toResolverFactory(annotation)
is TypedParameterResolver -> resolver.toResolverFactory(annotation)
}
}
// Add resolvers with their annotation
serviceContainer.getServiceNamesForAnnotation().forEach { resolverName ->
val annotation = serviceContainer.findAnnotationOnService(resolverName)
?: throwInternal("DI said ${annotationRef()} was present but isn't")
val resolver = serviceContainer.getService(resolverName, ParameterResolver::class)
addResolver(resolver, annotation)
}
factories += resolverFactories
logger.trace {
val resolversStr = compatibleInterfaces.joinToString("\n") { interfaceClass ->
buildString {
val factories = factories
.filter { factory -> factory.resolverType.isSubclassOf(interfaceClass) }
.sortedBy { it.resolverType.simpleNestedName }
appendLine("${interfaceClass.simpleNestedName} (${factories.size}):")
append(factories.joinAsList(linePrefix = "\t-") { "${it.resolverType.shortQualifiedName} ; priority ${it.priority} (${it.supportedTypesStr.joinToString()})" })
}
}
"Found resolvers:\n$resolversStr"
}
}
@Suppress("UNCHECKED_CAST")
internal fun > getResolverFactoryOrNull(resolverType: KClass, request: ResolverRequest): ParameterResolverFactory? {
val key = CacheKey(resolverType, request)
cache[key]?.let { return it as ParameterResolverFactory? }
val resolvableFactories = factories
.filter { it.resolverType.isSubclassOf(resolverType) }
.map { it as ParameterResolverFactory }
.filter { it.isResolvable(request) }
.let { resolvableFactories ->
if (resolvableFactories.isEmpty())
return@let resolvableFactories
// Keep most important factories, if two has same priority, it gets reported down
val maxPriority = resolvableFactories.maxOf { it.priority }
resolvableFactories.filter { it.priority == maxPriority }
}
require(resolvableFactories.size <= 1) {
val factoryNameList = resolvableFactories.joinAsList { it.resolverType.shortQualifiedName }
"Found multiple compatible resolvers, with the same priority\n$factoryNameList\nIncrease the priority of a resolver to override others"
}
val factory = resolvableFactories.firstOrNull()
cache[key] = factory
return factory
}
internal inline fun > hasResolverOfType(parameter: ParameterWrapper): Boolean {
return hasResolverOfType(ResolverRequest(parameter))
}
internal inline fun > hasResolverOfType(request: ResolverRequest): Boolean {
return getResolverFactoryOrNull(T::class, request) != null
}
internal inline fun > getResolverOfType(request: ResolverRequest): T {
return getResolver(T::class, request)
}
internal fun > getResolver(resolverType: KClass, request: ResolverRequest): T {
val factory = getResolverFactoryOrNull(resolverType, request)
if (factory == null) {
val wrapper = request.parameter
wrapper.throwUser("No ${resolverType.simpleNestedName} found for parameter '${wrapper.name}: ${wrapper.type.shortQualifiedName}'")
}
return factory.get(request)
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy