Kotlin JVM scripting support library that provides core functionality for executing and evaluating Kotlin scripts on the JVM platform
—
Script compilation caching interface with configurable cache implementations for improved performance in repeated script execution scenarios. The caching system supports both in-memory and persistent storage strategies.
Core interface for caching compiled JVM scripts with flexible storage and retrieval mechanisms.
/**
* Cache interface for compiled JVM scripts
* Provides storage and retrieval of compiled scripts to improve performance
*/
interface CompiledJvmScriptsCache {
/**
* Retrieve cached compiled script
* @param script Source code of the script
* @param scriptCompilationConfiguration Configuration used for compilation
* @return Cached compiled script or null if not found
*/
fun get(
script: SourceCode,
scriptCompilationConfiguration: ScriptCompilationConfiguration
): CompiledScript?
/**
* Store compiled script in cache
* @param compiledScript The compiled script to cache
* @param script Original source code
* @param scriptCompilationConfiguration Configuration used for compilation
*/
fun store(
compiledScript: CompiledScript,
script: SourceCode,
scriptCompilationConfiguration: ScriptCompilationConfiguration
)
/**
* No-operation cache implementation
* Does not store or retrieve any scripts
*/
object NoCache : CompiledJvmScriptsCache {
override fun get(
script: SourceCode,
scriptCompilationConfiguration: ScriptCompilationConfiguration
): CompiledScript? = null
override fun store(
compiledScript: CompiledScript,
script: SourceCode,
scriptCompilationConfiguration: ScriptCompilationConfiguration
) = Unit
}
}Usage Examples:
import kotlin.script.experimental.api.*
import kotlin.script.experimental.jvm.*
// Custom in-memory cache implementation
class InMemoryScriptCache : CompiledJvmScriptsCache {
private val cache = mutableMapOf<String, CompiledScript>()
private fun generateCacheKey(
script: SourceCode,
config: ScriptCompilationConfiguration
): String {
return "${script.text.hashCode()}-${config.hashCode()}"
}
override fun get(
script: SourceCode,
scriptCompilationConfiguration: ScriptCompilationConfiguration
): CompiledScript? {
val key = generateCacheKey(script, scriptCompilationConfiguration)
return cache[key]
}
override fun store(
compiledScript: CompiledScript,
script: SourceCode,
scriptCompilationConfiguration: ScriptCompilationConfiguration
) {
val key = generateCacheKey(script, scriptCompilationConfiguration)
cache[key] = compiledScript
}
}
// Use cache in compilation configuration
val scriptCache = InMemoryScriptCache()
val compilationConfig = ScriptCompilationConfiguration {
jvm {
compilationCache = scriptCache
}
}Configuration property for specifying cache implementation in script compilation settings.
/**
* Property key for compiled scripts cache in compilation configuration
*/
val ScriptCompilationConfiguration.compilationCache: PropertiesCollection.Key<CompiledJvmScriptsCache>Usage Example:
// Configure cache in compilation settings
val config = ScriptCompilationConfiguration {
compilationCache = InMemoryScriptCache()
// ... other configuration
}
// Use NoCache for no caching
val noCacheConfig = ScriptCompilationConfiguration {
compilationCache = CompiledJvmScriptsCache.NoCache
}import java.io.File
import java.security.MessageDigest
class FileBasedScriptCache(private val cacheDirectory: File) : CompiledJvmScriptsCache {
init {
cacheDirectory.mkdirs()
}
private fun generateCacheKey(
script: SourceCode,
config: ScriptCompilationConfiguration
): String {
val content = "${script.text}-${config.toString()}"
val digest = MessageDigest.getInstance("SHA-256")
val hash = digest.digest(content.toByteArray())
return hash.joinToString("") { "%02x".format(it) }
}
private fun getCacheFile(key: String): File {
return File(cacheDirectory, "$key.cached")
}
override fun get(
script: SourceCode,
scriptCompilationConfiguration: ScriptCompilationConfiguration
): CompiledScript? {
val key = generateCacheKey(script, scriptCompilationConfiguration)
val cacheFile = getCacheFile(key)
return if (cacheFile.exists()) {
try {
// Deserialize cached script
deserializeCompiledScript(cacheFile.readBytes())
} catch (e: Exception) {
// Cache corruption, remove file
cacheFile.delete()
null
}
} else {
null
}
}
override fun store(
compiledScript: CompiledScript,
script: SourceCode,
scriptCompilationConfiguration: ScriptCompilationConfiguration
) {
val key = generateCacheKey(script, scriptCompilationConfiguration)
val cacheFile = getCacheFile(key)
try {
val serializedScript = (compiledScript as KJvmCompiledScript).toBytes()
cacheFile.writeBytes(serializedScript)
} catch (e: Exception) {
// Failed to cache, continue without caching
println("Warning: Failed to cache script: ${e.message}")
}
}
fun clearCache() {
cacheDirectory.listFiles()?.forEach { it.delete() }
}
fun getCacheSize(): Long {
return cacheDirectory.listFiles()?.sumOf { it.length() } ?: 0L
}
}import java.util.LinkedHashMap
class LRUScriptCache(private val maxSize: Int = 100) : CompiledJvmScriptsCache {
private val cache = object : LinkedHashMap<String, CompiledScript>(16, 0.75f, true) {
override fun removeEldestEntry(eldest: MutableMap.MutableEntry<String, CompiledScript>): Boolean {
return size > maxSize
}
}
private fun generateCacheKey(
script: SourceCode,
config: ScriptCompilationConfiguration
): String {
return "${script.text.hashCode()}-${config.hashCode()}"
}
@Synchronized
override fun get(
script: SourceCode,
scriptCompilationConfiguration: ScriptCompilationConfiguration
): CompiledScript? {
val key = generateCacheKey(script, scriptCompilationConfiguration)
return cache[key]
}
@Synchronized
override fun store(
compiledScript: CompiledScript,
script: SourceCode,
scriptCompilationConfiguration: ScriptCompilationConfiguration
) {
val key = generateCacheKey(script, scriptCompilationConfiguration)
cache[key] = compiledScript
}
@Synchronized
fun clear() {
cache.clear()
}
@Synchronized
fun size(): Int = cache.size
@Synchronized
fun getStatistics(): CacheStatistics {
return CacheStatistics(
size = cache.size,
maxSize = maxSize,
hitRate = calculateHitRate()
)
}
}
data class CacheStatistics(
val size: Int,
val maxSize: Int,
val hitRate: Double
)class CompositeScriptCache(
private val primaryCache: CompiledJvmScriptsCache,
private val secondaryCache: CompiledJvmScriptsCache
) : CompiledJvmScriptsCache {
override fun get(
script: SourceCode,
scriptCompilationConfiguration: ScriptCompilationConfiguration
): CompiledScript? {
// Try primary cache first
primaryCache.get(script, scriptCompilationConfiguration)?.let { return it }
// Try secondary cache
val result = secondaryCache.get(script, scriptCompilationConfiguration)
// If found in secondary, promote to primary
result?.let {
primaryCache.store(it, script, scriptCompilationConfiguration)
}
return result
}
override fun store(
compiledScript: CompiledScript,
script: SourceCode,
scriptCompilationConfiguration: ScriptCompilationConfiguration
) {
// Store in both caches
primaryCache.store(compiledScript, script, scriptCompilationConfiguration)
secondaryCache.store(compiledScript, script, scriptCompilationConfiguration)
}
}
// Usage: Fast in-memory cache with persistent backup
val compositeCache = CompositeScriptCache(
primaryCache = LRUScriptCache(maxSize = 50),
secondaryCache = FileBasedScriptCache(File("script-cache"))
)import java.time.Instant
import java.time.Duration
import java.util.concurrent.ConcurrentHashMap
class ExpiringScriptCache(
private val ttl: Duration = Duration.ofHours(1)
) : CompiledJvmScriptsCache {
private data class CacheEntry(
val script: CompiledScript,
val timestamp: Instant
) {
fun isExpired(now: Instant): Boolean {
return Duration.between(timestamp, now) > ttl
}
}
private val cache = ConcurrentHashMap<String, CacheEntry>()
private fun generateCacheKey(
script: SourceCode,
config: ScriptCompilationConfiguration
): String {
return "${script.text.hashCode()}-${config.hashCode()}"
}
override fun get(
script: SourceCode,
scriptCompilationConfiguration: ScriptCompilationConfiguration
): CompiledScript? {
val key = generateCacheKey(script, scriptCompilationConfiguration)
val entry = cache[key]
val now = Instant.now()
return if (entry != null && !entry.isExpired(now)) {
entry.script
} else {
// Remove expired entry
cache.remove(key)
null
}
}
override fun store(
compiledScript: CompiledScript,
script: SourceCode,
scriptCompilationConfiguration: ScriptCompilationConfiguration
) {
val key = generateCacheKey(script, scriptCompilationConfiguration)
val entry = CacheEntry(compiledScript, Instant.now())
cache[key] = entry
}
fun cleanExpiredEntries() {
val now = Instant.now()
cache.entries.removeIf { (_, entry) -> entry.isExpired(now) }
}
}enum class CacheType {
NONE, IN_MEMORY, FILE_BASED, LRU, EXPIRING, COMPOSITE
}
data class CacheConfiguration(
val type: CacheType = CacheType.IN_MEMORY,
val maxSize: Int = 100,
val ttlHours: Long = 1,
val cacheDirectory: File? = null
)
object ScriptCacheFactory {
fun createCache(config: CacheConfiguration): CompiledJvmScriptsCache {
return when (config.type) {
CacheType.NONE -> CompiledJvmScriptsCache.NoCache
CacheType.IN_MEMORY -> InMemoryScriptCache()
CacheType.FILE_BASED -> {
val dir = config.cacheDirectory ?: File("script-cache")
FileBasedScriptCache(dir)
}
CacheType.LRU -> LRUScriptCache(config.maxSize)
CacheType.EXPIRING -> ExpiringScriptCache(Duration.ofHours(config.ttlHours))
CacheType.COMPOSITE -> CompositeScriptCache(
primaryCache = LRUScriptCache(config.maxSize),
secondaryCache = FileBasedScriptCache(
config.cacheDirectory ?: File("script-cache")
)
)
}
}
}
// Usage
val cacheConfig = CacheConfiguration(
type = CacheType.COMPOSITE,
maxSize = 200,
cacheDirectory = File("/tmp/kotlin-script-cache")
)
val cache = ScriptCacheFactory.createCache(cacheConfig)
val compilationConfig = ScriptCompilationConfiguration {
compilationCache = cache
}// Good: Include all relevant compilation factors
fun generateSecureCacheKey(
script: SourceCode,
config: ScriptCompilationConfiguration
): String {
val content = buildString {
append(script.text)
append(config.jvm.jvmTarget.value ?: "")
append(config.dependencies.joinToString { it.toString() })
append(config.jvm.jdkHome.value?.absolutePath ?: "")
}
return MessageDigest.getInstance("SHA-256")
.digest(content.toByteArray())
.joinToString("") { "%02x".format(it) }
}class MonitoredScriptCache(
private val delegate: CompiledJvmScriptsCache,
private val metrics: CacheMetrics
) : CompiledJvmScriptsCache {
override fun get(
script: SourceCode,
scriptCompilationConfiguration: ScriptCompilationConfiguration
): CompiledScript? {
val result = delegate.get(script, scriptCompilationConfiguration)
if (result != null) {
metrics.recordHit()
} else {
metrics.recordMiss()
}
return result
}
override fun store(
compiledScript: CompiledScript,
script: SourceCode,
scriptCompilationConfiguration: ScriptCompilationConfiguration
) {
delegate.store(compiledScript, script, scriptCompilationConfiguration)
metrics.recordStore()
}
}Install with Tessl CLI
npx tessl i tessl/maven-org-jetbrains-kotlin--kotlin-scripting-jvm