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

kotlin.script.experimental.host.configurationFromTemplate.kt Maven / Gradle / Ivy

/*
 * Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
 * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
 */

package kotlin.script.experimental.host

import kotlin.reflect.KClass
import kotlin.script.experimental.annotations.KotlinScript
import kotlin.script.experimental.api.*
import kotlin.script.experimental.util.PropertiesCollection

private const val ERROR_MSG_PREFIX = "Unable to construct script definition: "

private const val ILLEGAL_CONFIG_ANN_ARG =
    "Illegal argument compilationConfiguration of the KotlinScript annotation: expecting an object or default-constructed class derived from ScriptCompilationConfiguration"

private const val SCRIPT_RUNTIME_TEMPLATES_PACKAGE = "kotlin.script.templates.standard"

@KotlinScript
private abstract class DummyScriptTemplate

/**
 * Creates compilation configuration from annotated script base class
 * @param baseClassType the annotated script base class to construct the configuration from
 * @param baseHostConfiguration scripting host configuration properties
 * @param contextClass optional context class to extract classloading strategy from
 * @param body optional configuration function to add more properties to the compilation configuration
 */
fun createCompilationConfigurationFromTemplate(
    baseClassType: KotlinType,
    baseHostConfiguration: ScriptingHostConfiguration,
    contextClass: KClass<*> = ScriptCompilationConfiguration::class,
    body: ScriptCompilationConfiguration.Builder.() -> Unit = {}
): ScriptCompilationConfiguration {

    val templateClass: KClass<*> = baseClassType.getTemplateClass(baseHostConfiguration, contextClass)

    val mainAnnotation: KotlinScript = templateClass.kotlinScriptAnnotation

    return ScriptCompilationConfiguration(scriptConfigInstance(mainAnnotation.compilationConfiguration)) {
        hostConfiguration.update { it.withDefaultsFrom(baseHostConfiguration) }
        propertiesFromTemplate(templateClass, baseClassType, mainAnnotation)
        body()
    }
}

/**
 * Creates evaluation configuration from annotated script base class
 * @param baseClassType the annotated script base class to construct the configuration from
 * @param baseHostConfiguration scripting host configuration properties
 * @param contextClass optional context class to extract classloading strategy from
 * @param body optional configuration function to add more properties to the evaluation configuration
 */
fun createEvaluationConfigurationFromTemplate(
    baseClassType: KotlinType,
    baseHostConfiguration: ScriptingHostConfiguration,
    contextClass: KClass<*> = ScriptEvaluationConfiguration::class,
    body: ScriptEvaluationConfiguration.Builder.() -> Unit = {}
): ScriptEvaluationConfiguration {

    val templateClass: KClass<*> = baseClassType.getTemplateClass(baseHostConfiguration, contextClass)

    val mainAnnotation = templateClass.kotlinScriptAnnotation

    return ScriptEvaluationConfiguration(scriptConfigInstance(mainAnnotation.evaluationConfiguration)) {
        hostConfiguration.update { it.withDefaultsFrom(baseHostConfiguration) }
        body()
    }
}

private fun ScriptCompilationConfiguration.Builder.propertiesFromTemplate(
    templateClass: KClass<*>, baseClassType: KotlinType, mainAnnotation: KotlinScript
) {
    baseClass.replaceOnlyDefault(if (templateClass == baseClassType.fromClass) baseClassType else KotlinType(templateClass))
    fileExtension.replaceOnlyDefault(mainAnnotation.fileExtension)
    filePathPattern.replaceOnlyDefault(mainAnnotation.filePathPattern)
    displayName.replaceOnlyDefault(mainAnnotation.displayName)
}

private val KClass<*>.kotlinScriptAnnotation: KotlinScript
    get() = findAnnotation()
        ?: when ([email protected]) {
            // Any is the default template, so use a default annotation
            Any::class.qualifiedName,
            // transitions to the new scripting API: substituting annotations for standard templates from script-runtime
            "$SCRIPT_RUNTIME_TEMPLATES_PACKAGE.SimpleScriptTemplate",
            "$SCRIPT_RUNTIME_TEMPLATES_PACKAGE.ScriptTemplateWithArgs",
            "$SCRIPT_RUNTIME_TEMPLATES_PACKAGE.ScriptTemplateWithBindings" -> DummyScriptTemplate::class.findAnnotation()
            else -> null
        }
        ?: throw IllegalArgumentException("${ERROR_MSG_PREFIX}Expecting KotlinScript annotation on the ${this}")

private fun KotlinType.getTemplateClass(hostConfiguration: ScriptingHostConfiguration, contextClass: KClass<*>): KClass<*> {
    val getScriptingClass = hostConfiguration[ScriptingHostConfiguration.getScriptingClass]
        ?: throw IllegalArgumentException("${ERROR_MSG_PREFIX}Expecting 'getScriptingClass' parameter in the scripting host configuration")

    return try {
        getScriptingClass(this, contextClass, hostConfiguration)
    } catch (e: Throwable) {
        throw IllegalArgumentException("${ERROR_MSG_PREFIX}Unable to load base class ${this}", e)
    }
}

private inline fun  KClass<*>.findAnnotation(): T? =
    @Suppress("UNCHECKED_CAST")
    this.java.annotations.firstOrNull { it is T } as T?

private fun  KClass.createInstance(): T {
    // TODO: throw a meaningful exception
    val noArgsConstructor = java.constructors.singleOrNull { it.parameters.isEmpty() }
        ?: throw IllegalArgumentException("Class should have a single no-arg constructor: $this")

    @Suppress("UNCHECKED_CAST")
    return noArgsConstructor.newInstance() as T
}

private fun  scriptConfigInstance(kclass: KClass): T = try {
    kclass.objectInstance ?: kclass.createInstance()
} catch (e: Throwable) {
    throw IllegalArgumentException("$ILLEGAL_CONFIG_ANN_ARG: ${e.message + if (e.cause != null) " (${e.cause})" else ""}", e)
}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy