org.jetbrains.kotlinx.jupyter.libraries.LibrariesScanner.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of kotlin-jupyter-kernel Show documentation
Show all versions of kotlin-jupyter-kernel Show documentation
Kotlin Jupyter kernel published as artifact
package org.jetbrains.kotlinx.jupyter.libraries
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import org.jetbrains.kotlinx.jupyter.api.KotlinKernelHost
import org.jetbrains.kotlinx.jupyter.api.Notebook
import org.jetbrains.kotlinx.jupyter.api.TypeName
import org.jetbrains.kotlinx.jupyter.api.libraries.KOTLIN_JUPYTER_LIBRARIES_FILE_NAME
import org.jetbrains.kotlinx.jupyter.api.libraries.KOTLIN_JUPYTER_RESOURCES_PATH
import org.jetbrains.kotlinx.jupyter.api.libraries.LibrariesDefinitionDeclaration
import org.jetbrains.kotlinx.jupyter.api.libraries.LibrariesInstantiable
import org.jetbrains.kotlinx.jupyter.api.libraries.LibrariesProducerDeclaration
import org.jetbrains.kotlinx.jupyter.api.libraries.LibrariesScanResult
import org.jetbrains.kotlinx.jupyter.api.libraries.LibraryDefinition
import org.jetbrains.kotlinx.jupyter.config.errorForUser
import org.jetbrains.kotlinx.jupyter.config.getLogger
import org.jetbrains.kotlinx.jupyter.exceptions.ReplException
class LibrariesScanner(val notebook: Notebook) {
private val processedFQNs = mutableSetOf()
private fun > Iterable.filterProcessed(): List {
return filter { processedFQNs.add(it.fqn) }
}
fun addLibrariesFromClassLoader(classLoader: ClassLoader, host: KotlinKernelHost) {
val scanResult = scanForLibraries(classLoader)
log.debug("Scanning for libraries is done. Detected FQNs: ${Json.encodeToString(scanResult)}")
val libraries = instantiateLibraries(classLoader, scanResult, notebook)
log.debug("Number of detected definitions: ${libraries.size}")
libraries.forEach { host.addLibrary(it) }
}
private fun scanForLibraries(classLoader: ClassLoader): LibrariesScanResult {
val results = classLoader.getResources("$KOTLIN_JUPYTER_RESOURCES_PATH/$KOTLIN_JUPYTER_LIBRARIES_FILE_NAME").toList().map { url ->
val contents = url.readText()
Json.decodeFromString(contents)
}
val definitions = mutableListOf()
val producers = mutableListOf()
for (result in results) {
definitions.addAll(result.definitions)
producers.addAll(result.producers)
}
return LibrariesScanResult(
definitions.filterProcessed(),
producers.filterProcessed(),
)
}
private fun instantiateLibraries(classLoader: ClassLoader, scanResult: LibrariesScanResult, notebook: Notebook): List {
val definitions = mutableListOf()
fun withErrorsHandling(declaration: LibrariesInstantiable<*>, action: () -> T): T {
return try {
action()
} catch (e: Throwable) {
val errorMessage = "Failed to load library integration class '${declaration.fqn}'"
log.errorForUser(message = errorMessage, throwable = e)
throw ReplException(errorMessage, e)
}
}
scanResult.definitions.mapTo(definitions) { declaration ->
withErrorsHandling(declaration) {
instantiate(classLoader, declaration, notebook)
}
}
scanResult.producers.forEach { declaration ->
withErrorsHandling(declaration) {
val producer = instantiate(classLoader, declaration, notebook)
producer.getDefinitions(notebook).forEach {
definitions.add(it)
}
}
}
return definitions
}
private fun instantiate(classLoader: ClassLoader, data: LibrariesInstantiable, notebook: Notebook): T {
val clazz = classLoader.loadClass(data.fqn)
val constructors = clazz.constructors
if (constructors.isEmpty()) {
@Suppress("UNCHECKED_CAST")
return clazz.kotlin.objectInstance as T
}
val constructor = constructors.single()
@Suppress("UNCHECKED_CAST")
return when (constructor.parameterCount) {
0 -> {
constructor.newInstance()
}
1 -> {
constructor.newInstance(notebook)
}
else -> throw IllegalStateException("Only zero or one argument is allowed for library class")
} as T
}
companion object {
private val log = getLogger("libraries scanning")
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy