Refactor host configuration handling and script definition creation
so it is possible now to create custom host configuration with template and all configurations are handled consistenly. Also introduce and use new ScriptDefinition class wrapping compilation and evaluation configurations, that could be consistenly created from a template.
This commit is contained in:
+3
-1
@@ -10,6 +10,7 @@ package kotlin.script.experimental.annotations
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.script.experimental.api.ScriptCompilationConfiguration
|
||||
import kotlin.script.experimental.api.ScriptEvaluationConfiguration
|
||||
import kotlin.script.experimental.host.ScriptingHostConfiguration
|
||||
|
||||
/**
|
||||
* The annotation for declaring a script definition (template)
|
||||
@@ -52,6 +53,7 @@ annotation class KotlinScript(
|
||||
val fileExtension: String = "kts",
|
||||
val filePathPattern: String = "",
|
||||
val compilationConfiguration: KClass<out ScriptCompilationConfiguration> = ScriptCompilationConfiguration.Default::class,
|
||||
val evaluationConfiguration: KClass<out ScriptEvaluationConfiguration> = ScriptEvaluationConfiguration.Default::class
|
||||
val evaluationConfiguration: KClass<out ScriptEvaluationConfiguration> = ScriptEvaluationConfiguration.Default::class,
|
||||
val hostConfiguration: KClass<out ScriptingHostConfiguration> = ScriptingHostConfiguration::class
|
||||
)
|
||||
|
||||
|
||||
+109
-35
@@ -10,18 +10,44 @@ 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: "
|
||||
/**
|
||||
* Script definition combines configuration data for script compilation and evaluation
|
||||
*/
|
||||
data class ScriptDefinition(
|
||||
val compilationConfiguration: ScriptCompilationConfiguration,
|
||||
val evaluationConfiguration: ScriptEvaluationConfiguration
|
||||
)
|
||||
|
||||
private const val ILLEGAL_CONFIG_ANN_ARG =
|
||||
"Illegal argument compilationConfiguration of the KotlinScript annotation: expecting an object or default-constructed class derived from ScriptCompilationConfiguration"
|
||||
/**
|
||||
* Creates script compilation and evaluation configuration from annotated script base class
|
||||
* @param baseClassType the annotated script base class to construct the configuration from
|
||||
* @param baseHostConfiguration base scripting host configuration properties
|
||||
* @param contextClass optional context class to extract classloading strategy from
|
||||
* @param compilation optional configuration function to add more properties to the compilation configuration
|
||||
* @param evaluation optional configuration function to add more properties to the evaluation configuration
|
||||
*/
|
||||
fun createScriptDefinitionFromTemplate(
|
||||
baseClassType: KotlinType,
|
||||
baseHostConfiguration: ScriptingHostConfiguration,
|
||||
contextClass: KClass<*> = ScriptDefinition::class,
|
||||
compilation: ScriptCompilationConfiguration.Builder.() -> Unit = {},
|
||||
evaluation: ScriptEvaluationConfiguration.Builder.() -> Unit = {}
|
||||
): ScriptDefinition {
|
||||
val templateClass: KClass<*> = baseClassType.getTemplateClass(baseHostConfiguration, contextClass)
|
||||
val mainAnnotation = templateClass.kotlinScriptAnnotation
|
||||
|
||||
private const val SCRIPT_RUNTIME_TEMPLATES_PACKAGE = "kotlin.script.templates.standard"
|
||||
val hostConfiguration = constructHostConfiguration(mainAnnotation.hostConfiguration, baseHostConfiguration) {}
|
||||
|
||||
@KotlinScript
|
||||
private abstract class DummyScriptTemplate
|
||||
val compilationConfiguration =
|
||||
constructCompilationConfiguration(mainAnnotation, hostConfiguration, templateClass, baseClassType, compilation)
|
||||
val evaluationConfiguration = constructEvaluationConfiguration(mainAnnotation, hostConfiguration, evaluation)
|
||||
|
||||
return ScriptDefinition(compilationConfiguration, evaluationConfiguration)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates compilation configuration from annotated script base class
|
||||
* NOTE: it is preferable to use createScriptDefinitionFromTemplate for creating all configurations at once
|
||||
* @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
|
||||
@@ -33,20 +59,17 @@ fun createCompilationConfigurationFromTemplate(
|
||||
contextClass: KClass<*> = ScriptCompilationConfiguration::class,
|
||||
body: ScriptCompilationConfiguration.Builder.() -> Unit = {}
|
||||
): ScriptCompilationConfiguration {
|
||||
|
||||
val templateClass: KClass<*> = baseClassType.getTemplateClass(baseHostConfiguration, contextClass)
|
||||
val mainAnnotation = templateClass.kotlinScriptAnnotation
|
||||
|
||||
val mainAnnotation: KotlinScript = templateClass.kotlinScriptAnnotation
|
||||
val hostConfiguration = constructHostConfiguration(mainAnnotation.hostConfiguration, baseHostConfiguration) {}
|
||||
|
||||
return ScriptCompilationConfiguration(scriptConfigInstance(mainAnnotation.compilationConfiguration)) {
|
||||
hostConfiguration.update { it.withDefaultsFrom(baseHostConfiguration) }
|
||||
propertiesFromTemplate(templateClass, baseClassType, mainAnnotation)
|
||||
body()
|
||||
}
|
||||
return constructCompilationConfiguration(mainAnnotation, hostConfiguration, templateClass, baseClassType, body)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates evaluation configuration from annotated script base class
|
||||
* NOTE: it is preferable to use createScriptDefinitionFromTemplate for creating all configurations at once
|
||||
* @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
|
||||
@@ -58,17 +81,76 @@ fun createEvaluationConfigurationFromTemplate(
|
||||
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) }
|
||||
val hostConfiguration = constructHostConfiguration(mainAnnotation.hostConfiguration, baseHostConfiguration) {}
|
||||
|
||||
return constructEvaluationConfiguration(mainAnnotation, hostConfiguration, body)
|
||||
}
|
||||
|
||||
private const val ERROR_MSG_PREFIX = "Unable to construct script definition: "
|
||||
|
||||
private const val SCRIPT_RUNTIME_TEMPLATES_PACKAGE = "kotlin.script.templates.standard"
|
||||
|
||||
@KotlinScript
|
||||
private abstract class DummyScriptTemplate
|
||||
|
||||
private fun constructCompilationConfiguration(
|
||||
mainAnnotation: KotlinScript,
|
||||
hostConfiguration: ScriptingHostConfiguration,
|
||||
templateClass: KClass<*>,
|
||||
baseClassType: KotlinType,
|
||||
body: ScriptCompilationConfiguration.Builder.() -> Unit
|
||||
): ScriptCompilationConfiguration {
|
||||
val compilationConfigurationInstance = scriptConfigInstance(mainAnnotation.compilationConfiguration)
|
||||
?: throw IllegalArgumentException("${ERROR_MSG_PREFIX}Illegal argument compilationConfiguration of the KotlinScript annotation: expecting an object or default-constructable class derived from ScriptCompilationConfiguration")
|
||||
|
||||
return ScriptCompilationConfiguration(compilationConfigurationInstance) {
|
||||
// TODO: consider deprecating host configuration updating here, it is better to do it via dedicated annotation parameter
|
||||
this.hostConfiguration.update { it.withDefaultsFrom(hostConfiguration) }
|
||||
propertiesFromTemplate(templateClass, baseClassType, mainAnnotation)
|
||||
body()
|
||||
}
|
||||
}
|
||||
|
||||
private fun constructEvaluationConfiguration(
|
||||
mainAnnotation: KotlinScript,
|
||||
hostConfiguration: ScriptingHostConfiguration,
|
||||
body: ScriptEvaluationConfiguration.Builder.() -> Unit
|
||||
): ScriptEvaluationConfiguration {
|
||||
val evaluationConfigurationInstance = scriptConfigInstance(mainAnnotation.evaluationConfiguration)
|
||||
?: throw IllegalArgumentException("${ERROR_MSG_PREFIX}Illegal argument evaluationConfiguration of the KotlinScript annotation: expecting an object or default-constructable class derived from ScriptEvaluationConfiguration")
|
||||
|
||||
return ScriptEvaluationConfiguration(evaluationConfigurationInstance) {
|
||||
// TODO: consider deprecating host configuration updating here, it is better to do it via dedicated annotation parameter
|
||||
this.hostConfiguration.update { it.withDefaultsFrom(hostConfiguration) }
|
||||
body()
|
||||
}
|
||||
}
|
||||
|
||||
private fun constructHostConfiguration(
|
||||
hostConfigurationKClass: KClass<out ScriptingHostConfiguration>,
|
||||
baseHostConfiguration: ScriptingHostConfiguration,
|
||||
body: ScriptingHostConfiguration.Builder.() -> Unit
|
||||
): ScriptingHostConfiguration {
|
||||
if (hostConfigurationKClass == ScriptingHostConfiguration::class)
|
||||
return ScriptingHostConfiguration(body).withDefaultsFrom(baseHostConfiguration)
|
||||
|
||||
val singleArgConstructor = hostConfigurationKClass.java.constructors.singleOrNull {
|
||||
it.parameters.isNotEmpty() && it.parameters.first().type.isAssignableFrom(ScriptingHostConfiguration::class.java)
|
||||
}
|
||||
|
||||
val hostConfigurationInstance =
|
||||
if (singleArgConstructor != null) singleArgConstructor.newInstance(baseHostConfiguration) as ScriptingHostConfiguration
|
||||
else scriptConfigInstance(hostConfigurationKClass)
|
||||
?: throw IllegalArgumentException("${ERROR_MSG_PREFIX}Illegal argument hostConfiguration of the KotlinScript annotation: expecting an object or a class derived from ScriptingHostConfiguration constructable without arguments or from a base configuration")
|
||||
|
||||
return hostConfigurationInstance.with {
|
||||
body()
|
||||
}.withDefaultsFrom(baseHostConfiguration)
|
||||
}
|
||||
|
||||
private fun ScriptCompilationConfiguration.Builder.propertiesFromTemplate(
|
||||
templateClass: KClass<*>, baseClassType: KotlinType, mainAnnotation: KotlinScript
|
||||
) {
|
||||
@@ -83,13 +165,14 @@ private val KClass<*>.kotlinScriptAnnotation: KotlinScript
|
||||
?: when (this@kotlinScriptAnnotation.qualifiedName) {
|
||||
// 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
|
||||
// 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<KotlinScript>()
|
||||
"$SCRIPT_RUNTIME_TEMPLATES_PACKAGE.ScriptTemplateWithBindings",
|
||||
-> DummyScriptTemplate::class.findAnnotation()
|
||||
else -> null
|
||||
}
|
||||
?: throw IllegalArgumentException("${ERROR_MSG_PREFIX}Expecting KotlinScript annotation on the ${this}")
|
||||
?: 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]
|
||||
@@ -98,7 +181,7 @@ private fun KotlinType.getTemplateClass(hostConfiguration: ScriptingHostConfigur
|
||||
return try {
|
||||
getScriptingClass(this, contextClass, hostConfiguration)
|
||||
} catch (e: Throwable) {
|
||||
throw IllegalArgumentException("${ERROR_MSG_PREFIX}Unable to load base class ${this}", e)
|
||||
throw IllegalArgumentException("${ERROR_MSG_PREFIX}Unable to load base class $this", e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,18 +189,9 @@ private inline fun <reified T : Annotation> KClass<*>.findAnnotation(): T? =
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
this.java.annotations.firstOrNull { it is T } as T?
|
||||
|
||||
private fun <T : Any> KClass<T>.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 <T : PropertiesCollection> scriptConfigInstance(kclass: KClass<out T>): 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)
|
||||
}
|
||||
private inline fun <reified T : PropertiesCollection> scriptConfigInstance(kclass: KClass<out T>): T? =
|
||||
kclass.objectInstance ?: run {
|
||||
val noArgsConstructor = kclass.java.constructors.singleOrNull { it.parameters.isEmpty() }
|
||||
noArgsConstructor?.let { it.newInstance() as T }
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ interface ScriptingHostConfigurationKeys
|
||||
* The container for script evaluation configuration
|
||||
* For usages see actual code examples
|
||||
*/
|
||||
class ScriptingHostConfiguration(baseScriptingConfigurations: Iterable<ScriptingHostConfiguration>, body: Builder.() -> Unit) :
|
||||
open class ScriptingHostConfiguration(baseScriptingConfigurations: Iterable<ScriptingHostConfiguration>, body: Builder.() -> Unit) :
|
||||
PropertiesCollection(Builder(baseScriptingConfigurations).apply(body).data) {
|
||||
|
||||
constructor(body: Builder.() -> Unit = {}) : this(emptyList(), body)
|
||||
|
||||
Reference in New Issue
Block a user