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)
}