Skip to content

Commit 4bfb16a

Browse files
committed
Check and add overloads using Java reflection
1 parent eae7629 commit 4bfb16a

File tree

9 files changed

+106
-7
lines changed

9 files changed

+106
-7
lines changed

BotCommands-core/src/main/kotlin/io/github/freya022/botcommands/api/commands/application/provider/AbstractApplicationCommandManager.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import io.github.freya022.botcommands.internal.commands.application.context.user
2626
import io.github.freya022.botcommands.internal.commands.application.context.user.builder.UserCommandBuilderImpl
2727
import io.github.freya022.botcommands.internal.commands.application.slash.TopLevelSlashCommandInfoImpl
2828
import io.github.freya022.botcommands.internal.commands.application.slash.builder.TopLevelSlashCommandBuilderImpl
29+
import io.github.freya022.botcommands.internal.core.annotations.SkipJavaReflectionOverload
2930
import net.dv8tion.jda.api.entities.Member
3031
import net.dv8tion.jda.api.entities.Message
3132
import net.dv8tion.jda.api.entities.User
@@ -75,6 +76,7 @@ sealed class AbstractApplicationCommandManager(val context: BContext) {
7576
*
7677
* @see JDASlashCommand @JDASlashCommand
7778
*/
79+
@SkipJavaReflectionOverload
7880
fun slashCommand(name: String, function: KFunction<Any>?, builder: TopLevelSlashCommandBuilder.() -> Unit) {
7981
TopLevelSlashCommandBuilderImpl(this, name, function)
8082
.setCallerAsDeclarationSite()
@@ -106,6 +108,7 @@ sealed class AbstractApplicationCommandManager(val context: BContext) {
106108
*
107109
* @see JDAUserCommand @JDAUserCommand
108110
*/
111+
@SkipJavaReflectionOverload
109112
fun userCommand(name: String, function: KFunction<Any>, builder: UserCommandBuilder.() -> Unit) {
110113
UserCommandBuilderImpl(this, name, function)
111114
.setCallerAsDeclarationSite()
@@ -136,6 +139,7 @@ sealed class AbstractApplicationCommandManager(val context: BContext) {
136139
*
137140
* @see JDAMessageCommand @JDAMessageCommand
138141
*/
142+
@SkipJavaReflectionOverload
139143
fun messageCommand(name: String, function: KFunction<Any>, builder: MessageCommandBuilder.() -> Unit) {
140144
MessageCommandBuilderImpl(this, name, function)
141145
.setCallerAsDeclarationSite()
@@ -167,4 +171,4 @@ sealed class AbstractApplicationCommandManager(val context: BContext) {
167171
"${this.javaClass.simpleNestedName} only accepts the following integration types $supportedIntegrationTypes"
168172
}
169173
}
170-
}
174+
}

BotCommands-core/src/main/kotlin/io/github/freya022/botcommands/api/core/DeclarationSite.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.github.freya022.botcommands.api.core
22

33
import io.github.freya022.botcommands.api.core.annotations.IgnoreStackFrame
4+
import io.github.freya022.botcommands.internal.core.annotations.SkipJavaReflectionOverload
45
import io.github.freya022.botcommands.internal.utils.shortSignature
56
import kotlin.reflect.KFunction
67

@@ -19,11 +20,12 @@ class DeclarationSite private constructor(
1920

2021
companion object {
2122
@JvmStatic
23+
@SkipJavaReflectionOverload("Expected to be supplied with an Executable's function")
2224
fun fromFunctionSignature(function: KFunction<*>): DeclarationSite {
2325
return DeclarationSite(function.shortSignature)
2426
}
2527

2628
@JvmStatic
2729
fun fromRaw(string: String) = DeclarationSite(string)
2830
}
29-
}
31+
}

BotCommands-core/src/main/kotlin/io/github/freya022/botcommands/api/core/config/BServiceConfig.kt

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import io.github.freya022.botcommands.api.core.service.ServiceSupplier
66
import io.github.freya022.botcommands.api.core.service.annotations.*
77
import io.github.freya022.botcommands.api.core.utils.toImmutableMap
88
import io.github.freya022.botcommands.api.core.utils.unmodifiableView
9+
import io.github.freya022.botcommands.internal.core.annotations.SkipJavaReflectionOverload
910
import io.github.freya022.botcommands.internal.core.config.ConfigDSL
1011
import kotlin.reflect.KClass
1112

@@ -44,7 +45,7 @@ class BServiceConfigBuilder internal constructor() : BServiceConfig {
4445
* @param annotations Annotations which should be tied to this service
4546
* @param supplier The function supplying the service
4647
*/
47-
@JvmOverloads
48+
@SkipJavaReflectionOverload
4849
fun <T : Any> registerServiceSupplier(
4950
primaryType: KClass<T>,
5051
name: String = ServiceSupplier.defaultName(primaryType),
@@ -58,6 +59,36 @@ class BServiceConfigBuilder internal constructor() : BServiceConfig {
5859
_serviceSuppliers[primaryType] = ServiceSupplier(primaryType, name, additionalTypes, isPrimary, isLazy, priority, annotations, supplier)
5960
}
6061

62+
/**
63+
* Registers a supplier which gets loaded in the same manner as annotated service classes/factories.
64+
*
65+
* **Note:** The [annotations] passed will not be readable using standard reflection,
66+
* they are only read when functions
67+
* like [ServiceContainer.findAnnotationOnService] or [ServiceContainer.getServiceNamesForAnnotation] are used.
68+
*
69+
* @param primaryType The type as which the service will be *registered* as
70+
* @param name The [name][ServiceName] to register the service as
71+
* @param additionalTypes [Additional types][ServiceType] this service can be *retrieved* as
72+
* @param isPrimary Whether this service should be a [primary][Primary] service
73+
* @param isLazy Whether this service should be initialized only [when requested][Lazy]
74+
* @param priority The [priority][ServicePriority] of this service
75+
* @param annotations Annotations which should be tied to this service
76+
* @param supplier The function supplying the service
77+
*/
78+
@JvmOverloads
79+
fun <T : Any> registerServiceSupplier(
80+
primaryType: Class<T>,
81+
name: String = ServiceSupplier.defaultName(primaryType),
82+
additionalTypes: Set<Class<in T>> = emptySet(), // Accept superclasses not subclasses
83+
isPrimary: Boolean = false,
84+
isLazy: Boolean = false,
85+
priority: Int = 0,
86+
annotations: List<Annotation> = emptyList(),
87+
supplier: (BContext) -> T,
88+
) {
89+
_serviceSuppliers[primaryType.kotlin] = ServiceSupplier(primaryType.kotlin, name, additionalTypes.mapTo(hashSetOf()) { it.kotlin }, isPrimary, isLazy, priority, annotations, supplier)
90+
}
91+
6192
@JvmSynthetic
6293
internal fun build() = object : BServiceConfig {
6394
override val debug = this@BServiceConfigBuilder.debug

BotCommands-core/src/main/kotlin/io/github/freya022/botcommands/api/core/reflect/KotlinTypeToken.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.github.freya022.botcommands.api.core.reflect
22

3+
import io.github.freya022.botcommands.internal.core.annotations.SkipJavaReflectionOverload
34
import java.lang.reflect.ParameterizedType
45
import java.lang.reflect.Type
56
import kotlin.reflect.KClass
@@ -117,12 +118,14 @@ open class KotlinTypeToken<T> {
117118
* this is equivalent to [`clazz.startProjectedType`][KClass.starProjectedType].
118119
*/
119120
@JvmStatic
121+
@SkipJavaReflectionOverload
120122
fun ofKClass(clazz: KClass<*>) = KotlinTypeToken<Any>(clazz.starProjectedType)
121123

122124
/**
123125
* Creates a [KotlinTypeToken] with the given [KType], unchanged.
124126
*/
125127
@JvmStatic
128+
@SkipJavaReflectionOverload
126129
fun ofType(type: KType) = KotlinTypeToken<Any>(type)
127130
}
128-
}
131+
}

BotCommands-core/src/main/kotlin/io/github/freya022/botcommands/api/core/reflect/ParameterType.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.github.freya022.botcommands.api.core.reflect
22

3+
import io.github.freya022.botcommands.internal.core.annotations.SkipJavaReflectionOverload
34
import kotlin.reflect.KClass
45
import kotlin.reflect.KType
56
import kotlin.reflect.KTypeProjection
@@ -46,9 +47,11 @@ class ParameterType private constructor(val type: KType) {
4647
)
4748

4849
@JvmStatic
50+
@SkipJavaReflectionOverload
4951
fun ofKClass(clazz: KClass<*>) = ParameterType(clazz.starProjectedType)
5052

5153
@JvmStatic
54+
@SkipJavaReflectionOverload
5255
fun ofType(type: KType) = ParameterType(type)
5356
}
54-
}
57+
}

BotCommands-core/src/main/kotlin/io/github/freya022/botcommands/api/core/service/ServiceResult.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package io.github.freya022.botcommands.api.core.service
22

33
import io.github.freya022.botcommands.api.core.utils.joinAsList
44
import io.github.freya022.botcommands.api.core.utils.simpleNestedName
5+
import io.github.freya022.botcommands.internal.core.annotations.SkipJavaReflectionOverload
56
import io.github.freya022.botcommands.internal.core.exceptions.ServiceException
67
import io.github.freya022.botcommands.internal.utils.shortSignature
78
import io.github.freya022.botcommands.internal.utils.throwInternal
@@ -31,12 +32,14 @@ class ServiceError private constructor(
3132
FAILED_FATAL_CUSTOM_CONDITION("At least one custom check returned an error message, and was configured to fail");
3233

3334
@JvmOverloads
35+
@SkipJavaReflectionOverload
3436
fun toError(errorMessage: String, extraMessage: String? = null, failedFunction: KFunction<*>? = null, nestedError: ServiceError? = null, siblingErrors: List<ServiceError> = emptyList(), extra: Map<String, Any> = emptyMap()) =
3537
ServiceError(this, errorMessage, nestedError, siblingErrors, buildMap(2) {
3638
if (extraMessage != null) put("Extra message", extraMessage)
3739
if (failedFunction != null) put("Failed function", failedFunction)
3840
} + extra)
3941

42+
@SkipJavaReflectionOverload
4043
fun <T : Any> toResult(errorMessage: String, extraMessage: String? = null, failedFunction: KFunction<*>? = null, nestedError: ServiceError? = null, siblingErrors: List<ServiceError> = emptyList(), extra: Map<String, Any> = emptyMap()) =
4144
ServiceResult.fail<T>(toError(errorMessage, extraMessage, failedFunction, nestedError, siblingErrors, extra))
4245
}
@@ -160,4 +163,4 @@ class ServiceResult<out T : Any> private constructor(val service: T?, val servic
160163
fun <T : Any> fail(error: ServiceError) = ServiceResult<T>(null, error)
161164
fun <T : Any> fail(errors: List<ServiceError>) = fail<T>(ServiceError.fromErrors(errors))
162165
}
163-
}
166+
}

BotCommands-core/src/main/kotlin/io/github/freya022/botcommands/api/core/service/ServiceSupplier.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import io.github.freya022.botcommands.api.core.BContext
44
import io.github.freya022.botcommands.api.core.config.BServiceConfigBuilder
55
import io.github.freya022.botcommands.api.core.service.annotations.*
66
import io.github.freya022.botcommands.api.core.utils.simpleNestedName
7+
import io.github.freya022.botcommands.internal.core.annotations.SkipJavaReflectionOverload
78
import kotlin.reflect.KClass
89

910
/**
@@ -68,6 +69,7 @@ class ServiceSupplier<T : Any> @JvmOverloads constructor(
6869
}
6970

7071
companion object {
72+
@SkipJavaReflectionOverload
7173
fun defaultName(clazz: KClass<*>): String {
7274
return clazz.simpleNestedName.replaceFirstChar { it.lowercase() }
7375
}
@@ -77,4 +79,4 @@ class ServiceSupplier<T : Any> @JvmOverloads constructor(
7779
return clazz.simpleNestedName.replaceFirstChar { it.lowercase() }
7880
}
7981
}
80-
}
82+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package io.github.freya022.botcommands.internal.core.annotations
2+
3+
/**
4+
* Marker to ensure functions requesting kotlin-reflect instances have a Java equivalent.
5+
*
6+
* The test essentially makes sure the contributor did not forget about adding a Java equivalent.
7+
*/
8+
@Retention(AnnotationRetention.SOURCE)
9+
annotation class SkipJavaReflectionOverload(val reason: String = "")
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package io.github.freya022.botcommands.arch
2+
3+
import com.lemonappdev.konsist.api.Konsist
4+
import com.lemonappdev.konsist.api.declaration.KoTypeArgumentDeclaration
5+
import com.lemonappdev.konsist.api.ext.list.functions
6+
import com.lemonappdev.konsist.api.ext.list.withParameter
7+
import com.lemonappdev.konsist.api.ext.list.withoutAnnotationNamed
8+
import com.lemonappdev.konsist.api.provider.*
9+
import com.lemonappdev.konsist.api.verify.assertEmpty
10+
import com.lemonappdev.konsist.api.verify.assertNotEmpty
11+
import io.github.freya022.botcommands.internal.core.annotations.SkipJavaReflectionOverload
12+
import kotlin.test.Test
13+
14+
class JavaInteropTest {
15+
16+
@Test
17+
fun `Check all functions accepting kotlin-reflect instances have Java equivalents`() {
18+
Konsist.scopeFromProduction()
19+
.classes(includeNested = true)
20+
.filter { it.packagee!!.name.contains("api") }
21+
.functions(includeNested = true)
22+
.withoutAnnotationNamed(SkipJavaReflectionOverload::class.java.simpleName, JvmSynthetic::class.java.simpleName)
23+
.also { it.assertNotEmpty(strict = true) }
24+
.withParameter {
25+
it.getAllTypes().any { type ->
26+
val typeSource = (type as KoSourceDeclarationProvider).sourceDeclaration as? KoPackageProvider ?: return@any false
27+
28+
typeSource.packagee!!.hasNameStartingWith("kotlin.reflect")
29+
}
30+
}
31+
.assertEmpty(strict = true)
32+
}
33+
34+
fun KoNonNullableTypeProvider.getAllTypes(): List<KoBaseProvider> {
35+
fun KoTypeArgumentProvider.allArgumentTypes(): List<KoTypeArgumentDeclaration> {
36+
val args = typeArguments ?: return emptyList()
37+
return args + args.flatMap { it.allArgumentTypes() }
38+
}
39+
40+
return listOf(this.type) + this.type.allArgumentTypes()
41+
}
42+
}

0 commit comments

Comments
 (0)