All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jetbrains.kotlinx.jupyter.libraries.LibrariesScanner.kt Maven / Gradle / Ivy

There is a newer version: 0.12.0-356
Show newest version
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")
    }
}