Skip to content

Commit

Permalink
WIP API!
Browse files Browse the repository at this point in the history
  • Loading branch information
whyoleg committed Jan 24, 2025
1 parent a43dc97 commit e7f0ba3
Show file tree
Hide file tree
Showing 10 changed files with 427 additions and 424 deletions.
5 changes: 0 additions & 5 deletions sweetspi-runtime/src/commonMain/kotlin/Annotations.kt
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,6 @@ public annotation class Service
@Retention(AnnotationRetention.BINARY)
public annotation class ServiceProvider(public vararg val services: KClass<*>)

@MustBeDocumented
@Target(AnnotationTarget.ANNOTATION_CLASS)
@Retention(AnnotationRetention.BINARY)
public annotation class ServiceInfo

// !!!just for JVM interop!!!
// if the annotation is present, then there will be restrictions on how it could be used by ServiceProvider
// TODO: better name?
Expand Down
14 changes: 13 additions & 1 deletion sweetspi-runtime/src/commonMain/kotlin/ServiceBootstrap.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@ public interface ServiceBootstrap<T : Any> {
*
* @param value A lambda function that returns an instance of the service type [T].
*/
// fails if it was already initialized
public fun bootstrap(value: () -> T)

// returns true, if it was successfully initialized
public fun tryBootstrap(value: () -> T): Boolean
}

/**
Expand All @@ -34,7 +38,7 @@ public interface ServiceBootstrap<T : Any> {
*
* @param value The instance of the service to be used for bootstrapping.
*/
public inline fun <T : Any> ServiceBootstrap<T>.bootstrap(value: T) {
public /*inline*/ fun <T : Any> ServiceBootstrap<T>.bootstrap(value: T) {
bootstrap { value }
}

Expand All @@ -52,6 +56,7 @@ public inline fun <T : Any> ServiceBootstrap<List<T>>.bootstrapList(crossinline
bootstrap { buildList(builderAction) }
}

// TODO: double-initialization
// TODO: guard with lock and may be optimize a bit (atomicfu)
public abstract class DefaultServiceBootstrap<T : Any> : ServiceBootstrap<T> {
private var bootstrapValueProvider: (() -> T)? = null
Expand All @@ -65,4 +70,11 @@ public abstract class DefaultServiceBootstrap<T : Any> : ServiceBootstrap<T> {

bootstrapValueProvider = value
}

public override fun tryBootstrap(value: () -> T): Boolean {
if (bootstrapValueProvider != null) return false
if (lazyValue.isInitialized()) return false
bootstrapValueProvider = value
return true
}
}
64 changes: 24 additions & 40 deletions sweetspi-runtime/src/commonMain/kotlin/ServiceLoader.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,8 @@

package dev.whyoleg.sweetspi

import kotlin.jvm.*
import kotlin.reflect.*

// TODO: may be add ServiceRegistry to be able to dynamically add service implementations at runtime - similar to bootstrap

/**
* Provides functionality for dynamically loading service implementations using the Service Provider Interface (SPI) mechanism.
*
Expand All @@ -22,13 +19,8 @@ import kotlin.reflect.*
* @Service
* interface SimpleService {
* fun saySomethingSweet()
* companion object // needed for fast accessor
* }
*
* // will generate with the same visibility as an companion object ^_^
*
* fun SimpleService.Companion.serviceLoader(): ServiceLoader<SimpleService>
*
* // module: A, B or even in a library!
* @ServiceProvider
* object SimpleServiceImpl : SimpleService {
Expand All @@ -39,45 +31,37 @@ import kotlin.reflect.*
*
* // module: A, B, C or may be not even in your codebase...
* fun main() {
* SimpleService.serviceLoader().load().forEach { service ->
* ServiceLoader.loadAll<SimpleService>().forEach { service ->
* service.saySomethingSweet()
* }
* }
* ```
*/
public expect class ServiceLoader<T : Any> {
// shortcuts for most frequent operations (could be optimized)

// TODO: throw a nice exception?
public fun loadFirst(): T
public fun loadFirstOrNull(): T?
public fun loadAll(): List<T>

// more advanced usage
public interface ServiceLoader {
public fun <T : Any> load(cls: KClass<T>): Sequence<T>
public fun <T : Any> loadAll(cls: KClass<T>): List<T> = load(cls).toList()
public fun <T : Any> loadFirst(cls: KClass<T>): T = load(cls).first()
public fun <T : Any> loadFirstOrNull(cls: KClass<T>): T? = load(cls).firstOrNull()

/**
* Retrieves a list of services of the specified type [T], which must be annotated with [Service].
* Providers of these services must be annotated with [ServiceProvider].
*/
public fun loadInstances(): Sequence<T>
public fun loadInitializers(): Sequence<ServiceInitializer<T>>
// intrinsic candidates on JVM (may be just `reified` calls)
// it should work even without CP, but could be slower
// CP should validate that the class is annotated with `@Service`?
public companion object Default : ServiceLoader {
override fun <T : Any> load(cls: KClass<T>): Sequence<T> = DefaultServiceLoader.load(cls)
override fun <T : Any> loadAll(cls: KClass<T>): List<T> = DefaultServiceLoader.loadAll(cls)
override fun <T : Any> loadFirst(cls: KClass<T>): T = DefaultServiceLoader.loadFirst(cls)
override fun <T : Any> loadFirstOrNull(cls: KClass<T>): T? = DefaultServiceLoader.loadFirstOrNull(cls)

public companion object {
// TODO: intrinsic candidates on JVM
public inline fun <reified T : Any> of(): ServiceLoader<T>
public fun <T : Any> of(cls: KClass<T>): ServiceLoader<T>

@Deprecated("", ReplaceWith("of<T>().loadAll()"))
public inline fun <reified T : Any> load(): List<T>

@Deprecated("", ReplaceWith("of(cls).loadAll()"))
@JvmStatic
public fun <T : Any> load(cls: KClass<T>): List<T>
public inline fun <reified T : Any> load(): Sequence<T> = load(T::class)
public inline fun <reified T : Any> loadAll(): List<T> = loadAll(T::class)
public inline fun <reified T : Any> loadFirst(): T = loadFirst(T::class)
public inline fun <reified T : Any> loadFirstOrNull(): T? = loadFirstOrNull(T::class)
}
}

// annotations are coming based on ServiceInfo
public interface ServiceInitializer<T : Any> {
public val annotations: List<Annotation>
public fun initialize(): T
}
public inline fun <reified T : Any> ServiceLoader.load(): Sequence<T> = load(T::class)
public inline fun <reified T : Any> ServiceLoader.loadAll(): List<T> = loadAll(T::class)
public inline fun <reified T : Any> ServiceLoader.loadFirst(): T = loadFirst(T::class)
public inline fun <reified T : Any> ServiceLoader.loadFirstOrNull(): T? = loadFirstOrNull(T::class)

internal expect val DefaultServiceLoader: ServiceLoader
13 changes: 13 additions & 0 deletions sweetspi-runtime/src/commonMain/kotlin/ServiceRegistry.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
* Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license.
*/

package dev.whyoleg.sweetspi

import kotlin.reflect.*

public interface ServiceRegistry : ServiceLoader {
// declare, that it's possible to use the service, validation is performed lazy on load
public fun <T : Any> registerService(cls: KClass<T>)
public fun <T : Any> registerServiceProvider(cls: KClass<T>, provider: () -> T)
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ package dev.whyoleg.sweetspi.internal
@RequiresOptIn("Internal API for KSP processor, should not be used outside", RequiresOptIn.Level.ERROR)
public annotation class InternalSweetSpiApi

@InternalSweetSpiApi
public expect object InternalServiceLoader
//@InternalSweetSpiApi
//public expect object InternalServiceLoader
74 changes: 42 additions & 32 deletions sweetspi-runtime/src/jvmMain/kotlin/ServiceLoader.jvm.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,51 @@
* Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license.
*/

@file:Suppress("Since15")

package dev.whyoleg.sweetspi

import dev.whyoleg.sweetspi.internal.*
import kotlin.reflect.*
import java.util.ServiceLoader as JServiceLoader

public fun ServiceLoader.Default.classLoaderBased(classLoader: ClassLoader): ServiceLoader = ClassLoaderBasedServiceLoader(classLoader)
//public fun ServiceLoader.Default.moduleBased(moduleLayer: ModuleLayer): ServiceLoader = ModuleBasedServiceLoader(moduleLayer)

internal actual val DefaultServiceLoader: ServiceLoader get() = DefaultServiceLoaderImpl

private object DefaultServiceLoaderImpl : AbstractServiceLoader() {
override fun <T : Any> load(cls: Class<T>): JServiceLoader<T> = JServiceLoader.load(cls, cls.classLoader)
}

private class ClassLoaderBasedServiceLoader(private val classLoader: ClassLoader) : AbstractServiceLoader() {
override fun <T : Any> load(cls: Class<T>): JServiceLoader<T> = JServiceLoader.load(cls, classLoader)
}

//private class ModuleBasedServiceLoader(private val moduleLayer: ModuleLayer) : AbstractServiceLoader() {
// override fun <T : Any> load(cls: Class<T>): JServiceLoader<T> = JServiceLoader.load(moduleLayer, cls)
//}

@Suppress("Since15")
@OptIn(InternalSweetSpiApi::class)
public actual class ServiceLoader<T : Any> internal constructor(
private val instances: Sequence<T>,
private val initializers: Sequence<ServiceInitializer<T>>,
) {
public actual fun loadFirst(): T = instances.first()
public actual fun loadFirstOrNull(): T? = instances.firstOrNull()
public actual fun loadAll(): List<T> = instances.toList()

public actual fun loadInstances(): Sequence<T> = instances
public actual fun loadInitializers(): Sequence<ServiceInitializer<T>> = initializers

public actual companion object {
// deprecated start
@JvmStatic
public actual fun <T : Any> load(cls: KClass<T>): List<T> = of(cls).loadAll()
public actual inline fun <reified T : Any> load(): List<T> = of<T>().loadAll()
// deprecated end

public actual inline fun <reified T : Any> of(): ServiceLoader<T> = of(T::class.java, T::class.java.classLoader)
public inline fun <reified T : Any> of(classLoader: ClassLoader): ServiceLoader<T> = of(T::class.java, classLoader)
public inline fun <reified T : Any> of(moduleLayer: ModuleLayer): ServiceLoader<T> = of(T::class.java, moduleLayer)

public actual fun <T : Any> of(cls: KClass<T>): ServiceLoader<T> = of(cls.java, cls.java.classLoader)
public fun <T : Any> of(cls: KClass<T>, classLoader: ClassLoader): ServiceLoader<T> = of(cls.java, classLoader)
public fun <T : Any> of(cls: KClass<T>, moduleLayer: ModuleLayer): ServiceLoader<T> = of(cls.java, moduleLayer)

public fun <T : Any> of(cls: Class<T>): ServiceLoader<T> = of(cls, cls.classLoader)
public fun <T : Any> of(cls: Class<T>, classLoader: ClassLoader): ServiceLoader<T> = InternalServiceLoader.of(cls, classLoader)
public fun <T : Any> of(cls: Class<T>, moduleLayer: ModuleLayer): ServiceLoader<T> = InternalServiceLoader.of(cls, moduleLayer)
// TODO: handle initializer
private abstract class AbstractServiceLoader : ServiceLoader {
protected abstract fun <T : Any> load(cls: Class<T>): JServiceLoader<T>

final override fun <T : Any> load(cls: KClass<T>): Sequence<T> = Sequence { load(cls.java).iterator() }
}

public interface StringInitializer {
public fun initialize(): String
}

// @JvmService
private fun generated() {
Sequence {
JServiceLoader.load(String::class.java, String::class.java.classLoader).iterator()
}
}

// @Service
private fun generatedInitializer() {
Sequence {
JServiceLoader.load(StringInitializer::class.java, StringInitializer::class.java.classLoader).iterator()
}.map { it.initialize() }
}
Loading

0 comments on commit e7f0ba3

Please sign in to comment.