Skip to content

Commit

Permalink
Introduce GetScriptingClassByClassLoader interface
Browse files Browse the repository at this point in the history
It is needed to override default JVM behaviour
  • Loading branch information
ileasile authored and ligee committed Nov 28, 2020
1 parent 65ce7cd commit 89bba93
Show file tree
Hide file tree
Showing 3 changed files with 160 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/*
* Copyright 2010-2020 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.jvmhost.test

import junit.framework.TestCase
import kotlinx.coroutines.runBlocking
import java.io.BufferedOutputStream
import java.io.FileOutputStream
import java.nio.file.Files
import java.nio.file.Path
import kotlin.reflect.KClass
import kotlin.script.experimental.api.*
import kotlin.script.experimental.host.ScriptingHostConfiguration
import kotlin.script.experimental.host.getScriptingClass
import kotlin.script.experimental.host.with
import kotlin.script.experimental.jvm.*
import kotlin.script.experimental.jvm.impl.KJvmCompiledModuleInMemory
import kotlin.script.experimental.jvm.impl.KJvmCompiledScript
import kotlin.script.experimental.jvmhost.JvmScriptCompiler

/**
* This test shows an ability of using KClasses loaded with classloaders
* other than default one, in the role of implicit receivers. For this reason,
* specific [GetScriptingClassByClassLoader] was implemented. Actually,
* as we use previously compiled snippets as implicits, we could achieve the same
* thing if we change the used compiler to one of the REPL ones. But if we are limited
* in our choice of compiler and only can tune the configuration, this is the only way.
*
* This test may be deleted or at least simplified when the option
* in [ScriptCompilationConfiguration] for saving previous classes in
* underlying module will be introduced.
*/
class ImplicitsFromScriptResultTest : TestCase() {
fun testImplicits() {
val host = CompilerHost()

val snippets = listOf(
"val xyz0 = 42",
"fun f() = xyz0",
"val finalRes = xyz0 + f()",
)
for (snippet in snippets) {
val res = host.compile(snippet)
assertTrue(res is ResultWithDiagnostics.Success)
}
}
}

fun interface PreviousScriptClassesProvider {
fun get(): List<KClass<*>>
}

class GetScriptClassForImplicits(
private val previousScriptClassesProvider: PreviousScriptClassesProvider
) : GetScriptingClassByClassLoader {
private val getScriptingClass = JvmGetScriptingClass()

private val lastClassLoader
get() = previousScriptClassesProvider.get().lastOrNull()?.java?.classLoader

override fun invoke(
classType: KotlinType,
contextClass: KClass<*>,
hostConfiguration: ScriptingHostConfiguration
): KClass<*> {
return getScriptingClass(classType, lastClassLoader ?: contextClass.java.classLoader, hostConfiguration)
}

override fun invoke(
classType: KotlinType,
contextClassLoader: ClassLoader?,
hostConfiguration: ScriptingHostConfiguration
): KClass<*> {
return getScriptingClass(classType, lastClassLoader ?: contextClassLoader, hostConfiguration)
}
}

class CompilerHost {
private var counter = 0
private val implicits = mutableListOf<KClass<*>>()
private val outputDir: Path = Files.createTempDirectory("kotlin-scripting-jvm")
private val classWriter = ClassWriter(outputDir)

init {
outputDir.toFile().deleteOnExit()
}

private val myHostConfiguration = defaultJvmScriptingHostConfiguration.with {
getScriptingClass(GetScriptClassForImplicits(::getImplicitsClasses))
}

private val compileConfiguration = ScriptCompilationConfiguration {
hostConfiguration(myHostConfiguration)

jvm {
dependencies(JvmDependency(outputDir.toFile()))
}
}

private val evaluationConfiguration = ScriptEvaluationConfiguration()

private val compiler = JvmScriptCompiler(myHostConfiguration)

private fun getImplicitsClasses(): List<KClass<*>> = implicits

fun compile(code: String): ResultWithDiagnostics<CompiledScript> {
val source = SourceCodeTestImpl(counter++, code)
val refinedConfig = compileConfiguration.with {
implicitReceivers(*implicits.toTypedArray())
}
val result = runBlocking { compiler.invoke(source, refinedConfig) }
val compiledScript = result.valueOrThrow() as KJvmCompiledScript

classWriter.writeCompiledSnippet(compiledScript)

val kClass = runBlocking { compiledScript.getClass(evaluationConfiguration) }.valueOrThrow()
implicits.add(kClass)
return result
}

private class SourceCodeTestImpl(number: Int, override val text: String) : SourceCode {
override val name: String = "Line_$number"
override val locationId: String = "location_$number"
}
}

class ClassWriter(private val outputDir: Path) {
fun writeCompiledSnippet(snippet: KJvmCompiledScript) {
val moduleInMemory = snippet.getCompiledModule() as KJvmCompiledModuleInMemory
moduleInMemory.compilerOutputFiles.forEach { (name, bytes) ->
if (name.endsWith(".class")) {
writeClass(bytes, outputDir.resolve(name))
}
}
}

private fun writeClass(classBytes: ByteArray, path: Path) {
FileOutputStream(path.toAbsolutePath().toString()).use { fos ->
BufferedOutputStream(fos).use { out ->
out.write(classBytes)
out.flush()
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,11 @@ val defaultJvmScriptingHostConfiguration
getScriptingClass(JvmGetScriptingClass())
}

class JvmGetScriptingClass : GetScriptingClass, Serializable {
interface GetScriptingClassByClassLoader : GetScriptingClass {
operator fun invoke(classType: KotlinType, contextClassLoader: ClassLoader?, hostConfiguration: ScriptingHostConfiguration): KClass<*>
}

class JvmGetScriptingClass : GetScriptingClassByClassLoader, Serializable {

@Transient
private var dependencies: List<ScriptDependency>? = null
Expand All @@ -64,7 +68,11 @@ class JvmGetScriptingClass : GetScriptingClass, Serializable {
invoke(classType, contextClass.java.classLoader, hostConfiguration)

@Synchronized
operator fun invoke(classType: KotlinType, contextClassLoader: ClassLoader?, hostConfiguration: ScriptingHostConfiguration): KClass<*> {
override operator fun invoke(
classType: KotlinType,
contextClassLoader: ClassLoader?,
hostConfiguration: ScriptingHostConfiguration
): KClass<*> {

// checking if class already loaded in the same context
val fromClass = classType.fromClass
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -352,8 +352,8 @@ fun getScriptCollectedData(
val hostConfiguration =
compilationConfiguration[ScriptCompilationConfiguration.hostConfiguration] ?: defaultJvmScriptingHostConfiguration
val getScriptingClass = hostConfiguration[ScriptingHostConfiguration.getScriptingClass]
val jvmGetScriptingClass = (getScriptingClass as? JvmGetScriptingClass)
?: throw IllegalArgumentException("Expecting JvmGetScriptingClass in the hostConfiguration[getScriptingClass], got $getScriptingClass")
val jvmGetScriptingClass = (getScriptingClass as? GetScriptingClassByClassLoader)
?: throw IllegalArgumentException("Expecting class implementing GetScriptingClassByClassLoader in the hostConfiguration[getScriptingClass], got $getScriptingClass")
val acceptedAnnotations =
compilationConfiguration[ScriptCompilationConfiguration.refineConfigurationOnAnnotations]?.flatMap {
it.annotations.mapNotNull { ann ->
Expand Down

0 comments on commit 89bba93

Please sign in to comment.