Kotlin Scripting JVM host for executing and compiling Kotlin scripts in JVM environments with JSR-223 integration and comprehensive caching mechanisms
—
The script caching system provides persistent storage mechanisms for compiled scripts to improve performance through JAR-based caching and avoid recompilation of unchanged scripts.
JAR-based cache implementation that stores compiled scripts as JAR files with dependency information and metadata.
/**
* Cache implementation that stores compiled scripts as JAR files
* @param scriptToFile Function mapping script and configuration to cache file location
*/
open class CompiledScriptJarsCache(
val scriptToFile: (SourceCode, ScriptCompilationConfiguration) -> File?
) : CompiledJvmScriptsCache {
/**
* Retrieves cached compiled script from JAR file
* @param script Source code to look up
* @param scriptCompilationConfiguration Compilation configuration for cache key
* @returns Cached compiled script or null if not found/invalid
*/
override fun get(script: SourceCode, scriptCompilationConfiguration: ScriptCompilationConfiguration): CompiledScript?
/**
* Stores compiled script to JAR file cache
* @param compiledScript Compiled script to cache
* @param script Original source code
* @param scriptCompilationConfiguration Compilation configuration used
*/
override fun store(
compiledScript: CompiledScript,
script: SourceCode,
scriptCompilationConfiguration: ScriptCompilationConfiguration
)
}Usage Examples:
import kotlin.script.experimental.jvmhost.CompiledScriptJarsCache
import kotlin.script.experimental.api.*
import java.io.File
import java.security.MessageDigest
// Create cache with hash-based file mapping
val cache = CompiledScriptJarsCache { script, config ->
// Generate cache file path based on script content and configuration
val hash = generateScriptHash(script, config)
File("cache/scripts", "$hash.jar")
}
fun generateScriptHash(script: SourceCode, config: ScriptCompilationConfiguration): String {
val digest = MessageDigest.getInstance("SHA-256")
digest.update(script.text.toByteArray())
digest.update(config.toString().toByteArray())
return digest.digest().joinToString("") { "%02x".format(it) }
}
// Use cache with compilation
val compiler = JvmScriptCompiler()
val script = "println(\"Hello, cached world!\")".toScriptSource()
val config = ScriptCompilationConfiguration {
dependencies(JvmDependency(kotlinStdlib))
}
// Try to get from cache first
val cachedScript = cache.get(script, config)
val compiledScript = if (cachedScript != null) {
println("Using cached script")
cachedScript
} else {
println("Compiling and caching script")
val compilationResult = compiler(script, config)
when (compilationResult) {
is ResultWithDiagnostics.Success -> {
val compiled = compilationResult.value
cache.store(compiled, script, config)
compiled
}
is ResultWithDiagnostics.Failure -> {
compilationResult.reports.forEach { println("Error: ${it.message}") }
return
}
}
}
// Advanced cache with versioning
class VersionedScriptCache(private val baseDir: File) : CompiledJvmScriptsCache {
private val scriptToFile: (SourceCode, ScriptCompilationConfiguration) -> File? = { script, config ->
val scriptHash = generateScriptHash(script, config)
val versionDir = File(baseDir, "v1") // Version for cache format
versionDir.mkdirs()
File(versionDir, "$scriptHash.jar")
}
private val delegate = CompiledScriptJarsCache(scriptToFile)
override fun get(script: SourceCode, scriptCompilationConfiguration: ScriptCompilationConfiguration): CompiledScript? {
return try {
delegate.get(script, scriptCompilationConfiguration)
} catch (e: Exception) {
// Handle cache corruption or version mismatch
println("Cache miss due to error: ${e.message}")
null
}
}
override fun store(compiledScript: CompiledScript, script: SourceCode, scriptCompilationConfiguration: ScriptCompilationConfiguration) {
try {
delegate.store(compiledScript, script, scriptCompilationConfiguration)
} catch (e: Exception) {
println("Failed to cache script: ${e.message}")
}
}
}
val versionedCache = VersionedScriptCache(File("versioned-cache"))Integrating script caching with the scripting host for automatic cache management.
Cache-Enabled Host Example:
import kotlin.script.experimental.jvmhost.BasicJvmScriptingHost
import kotlin.script.experimental.jvm.*
// Create host configuration with caching
val hostConfigWithCache = ScriptingHostConfiguration {
jvm {
compilationCache(CompiledScriptJarsCache { script, config ->
val scriptName = script.name?.substringBeforeLast('.') ?: "unnamed"
val hash = generateScriptHash(script, config).take(8)
File("cache/compiled", "${scriptName}_$hash.jar")
})
}
}
// Create host with caching enabled
val cachingHost = BasicJvmScriptingHost(
baseHostConfiguration = hostConfigWithCache
)
// Scripts will be automatically cached and retrieved
@KotlinScript(fileExtension = "kts")
class CachedScript
val result1 = cachingHost.evalWithTemplate<CachedScript>("println(\"First execution\")".toScriptSource())
val result2 = cachingHost.evalWithTemplate<CachedScript>("println(\"Second execution - cached!\")".toScriptSource())The cache stores compiled scripts as executable JAR files with embedded metadata and dependency information.
cached-script.jar
├── META-INF/
│ └── MANIFEST.MF # Manifest with Main-Class and Class-Path
├── script_metadata.json # Script compilation metadata
└── compiled/ # Compiled class files
├── Script.class # Main script class
└── Script$*.class # Inner classesManifest-Version: 1.0
Created-By: JetBrains Kotlin
Main-Class: Script
Class-Path: /path/to/kotlin-stdlib.jar /path/to/dependency.jarThe cache automatically handles loading scripts from JAR files with dependency resolution and validation.
/**
* Loads compiled script from JAR file
* @param checkMissingDependencies Whether to validate all dependencies exist
* @returns Loaded compiled script or null if invalid
*/
fun File.loadScriptFromJar(checkMissingDependencies: Boolean = true): CompiledScript?Manual JAR Loading Example:
import kotlin.script.experimental.jvmhost.loadScriptFromJar
import java.io.File
// Load script from cached JAR
val jarFile = File("cache/my-script.jar")
val loadedScript = jarFile.loadScriptFromJar(checkMissingDependencies = true)
when (loadedScript) {
null -> {
println("Failed to load script from JAR - possibly corrupted or missing dependencies")
}
else -> {
println("Successfully loaded script from cache")
// Get script class
val classResult = loadedScript.getClass()
when (classResult) {
is ResultWithDiagnostics.Success -> {
val scriptClass = classResult.value
println("Loaded script class: ${scriptClass.simpleName}")
// Execute if needed
val evaluator = BasicJvmScriptEvaluator()
val evalResult = evaluator(loadedScript, ScriptEvaluationConfiguration.Default)
// Handle evaluation result...
}
is ResultWithDiagnostics.Failure -> {
println("Failed to get script class from cached JAR")
}
}
}
}
// Load with dependency checking disabled (faster but less safe)
val quickLoadedScript = jarFile.loadScriptFromJar(checkMissingDependencies = false)Cached scripts are automatically invalidated when:
Custom Cache Invalidation:
class SmartScriptCache(private val baseDir: File) {
private val cache = CompiledScriptJarsCache { script, config ->
File(baseDir, "${script.name}-${config.hashCode()}.jar")
}
fun invalidateScript(scriptName: String) {
baseDir.listFiles { _, name ->
name.startsWith("$scriptName-") && name.endsWith(".jar")
}?.forEach { jarFile ->
if (jarFile.delete()) {
println("Invalidated cached script: ${jarFile.name}")
}
}
}
fun clearCache() {
baseDir.listFiles { _, name -> name.endsWith(".jar") }
?.forEach { it.delete() }
println("Cache cleared")
}
fun getCacheStats(): CacheStats {
val jarFiles = baseDir.listFiles { _, name -> name.endsWith(".jar") } ?: emptyArray()
return CacheStats(
totalFiles = jarFiles.size,
totalSize = jarFiles.sumOf { it.length() },
oldestFile = jarFiles.minByOrNull { it.lastModified() }?.lastModified(),
newestFile = jarFiles.maxByOrNull { it.lastModified() }?.lastModified()
)
}
}
data class CacheStats(
val totalFiles: Int,
val totalSize: Long,
val oldestFile: Long?,
val newestFile: Long?
)Performance Monitoring Example:
class MonitoredScriptCache(private val delegate: CompiledJvmScriptsCache) : CompiledJvmScriptsCache {
private var hits = 0
private var misses = 0
private var stores = 0
override fun get(script: SourceCode, scriptCompilationConfiguration: ScriptCompilationConfiguration): CompiledScript? {
val result = delegate.get(script, scriptCompilationConfiguration)
if (result != null) {
hits++
println("Cache hit (${hits}/${hits + misses} = ${(hits * 100) / (hits + misses)}%)")
} else {
misses++
println("Cache miss")
}
return result
}
override fun store(compiledScript: CompiledScript, script: SourceCode, scriptCompilationConfiguration: ScriptCompilationConfiguration) {
delegate.store(compiledScript, script, scriptCompilationConfiguration)
stores++
println("Cached script (total stored: $stores)")
}
fun getStats() = "Cache stats: $hits hits, $misses misses, $stores stored"
}Install with Tessl CLI
npx tessl i tessl/maven-org-jetbrains-kotlin--kotlin-scripting-jvm-host