Prepare repl interfaces and generic implementation for separation into possibly remote parts
This commit is contained in:
@@ -11,5 +11,6 @@
|
||||
<orderEntry type="library" scope="PROVIDED" name="intellij-core" level="project" />
|
||||
<orderEntry type="library" exported="" name="cli-parser" level="project" />
|
||||
<orderEntry type="library" name="jps" level="project" />
|
||||
<orderEntry type="module" module-name="descriptor.loader.java" />
|
||||
</component>
|
||||
</module>
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.cli.common.repl
|
||||
|
||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||
import java.io.File
|
||||
import java.net.URLClassLoader
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock
|
||||
import kotlin.concurrent.read
|
||||
import kotlin.concurrent.write
|
||||
|
||||
open class GenericReplCompiledEvaluator(baseClasspath: Iterable<File>) : ReplCompiledEvaluator {
|
||||
|
||||
private val classpath = baseClasspath.toMutableList()
|
||||
|
||||
private var classLoader: org.jetbrains.kotlin.cli.common.repl.ReplClassLoader =
|
||||
org.jetbrains.kotlin.cli.common.repl.ReplClassLoader(URLClassLoader(classpath.map { it.toURI().toURL() }.toTypedArray(), Thread.currentThread().contextClassLoader))
|
||||
private val classLoaderLock = ReentrantReadWriteLock()
|
||||
|
||||
private class ClassWithInstance(val klass: Class<*>, val instance: Any)
|
||||
|
||||
private val compiledLoadedClassesHistory = arrayListOf<Pair<ReplCodeLine, ClassWithInstance>>()
|
||||
|
||||
override fun eval(codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>, compiledClasses: List<CompiledClassData>, hasResult: Boolean): ReplEvalResult {
|
||||
|
||||
checkAndUpdateReplHistoryCollection(compiledLoadedClassesHistory, history)?.let {
|
||||
return@eval ReplEvalResult.HistoryMismatch(it)
|
||||
}
|
||||
|
||||
classLoaderLock.read {
|
||||
compiledClasses.filter { it.path.endsWith(".class") }
|
||||
.forEach {
|
||||
classLoader.addClass(JvmClassName.byInternalName(it.path.replaceFirst("\\.class$".toRegex(), "")), it.bytes)
|
||||
}
|
||||
}
|
||||
|
||||
val scriptClass = classLoaderLock.read { classLoader.loadClass("Line${codeLine.no}") }
|
||||
|
||||
val constructorParams = compiledLoadedClassesHistory.map { it.second.klass }.toTypedArray()
|
||||
val constructorArgs = compiledLoadedClassesHistory.map { it.second.instance }.toTypedArray()
|
||||
|
||||
val scriptInstanceConstructor = scriptClass.getConstructor(*constructorParams)
|
||||
val scriptInstance =
|
||||
try {
|
||||
evalWithIO { scriptInstanceConstructor.newInstance(*constructorArgs) }
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
// ignore everything in the stack trace until this constructor call
|
||||
return ReplEvalResult.Error.Runtime(renderReplStackTrace(e.cause!!, startFromMethodName = "${scriptClass.name}.<init>"))
|
||||
}
|
||||
|
||||
compiledLoadedClassesHistory.add(codeLine to ClassWithInstance(scriptClass, scriptInstance))
|
||||
|
||||
val rvField = scriptClass.getDeclaredField(SCRIPT_RESULT_FIELD_NAME).apply { isAccessible = true }
|
||||
val rv: Any? = rvField.get(scriptInstance)
|
||||
|
||||
return if (hasResult) ReplEvalResult.ValueResult(rv) else ReplEvalResult.UnitResult
|
||||
}
|
||||
|
||||
fun dependenciesAdded(newClasspath: List<File>) {
|
||||
classLoaderLock.write {
|
||||
classpath.addAll(newClasspath)
|
||||
classLoader = org.jetbrains.kotlin.cli.common.repl.ReplClassLoader(URLClassLoader(newClasspath.map { it.toURI().toURL() }.toTypedArray(), classLoader))
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val SCRIPT_RESULT_FIELD_NAME = "\$\$result"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.cli.common.repl
|
||||
|
||||
import java.io.File
|
||||
import java.io.Serializable
|
||||
import java.util.*
|
||||
|
||||
data class ReplCodeLine(val no: Int, val code: String)
|
||||
|
||||
data class CompiledClassData(val path: String, val bytes: ByteArray) : Serializable {
|
||||
override fun equals(other: Any?): Boolean = (other as? CompiledClassData)?.let { path == it.path && Arrays.equals(bytes, it.bytes) } ?: false
|
||||
override fun hashCode(): Int = path.hashCode() + Arrays.hashCode(bytes)
|
||||
}
|
||||
|
||||
sealed class ReplCheckResult : Serializable {
|
||||
object Ok : ReplCheckResult()
|
||||
object Incomplete : ReplCheckResult()
|
||||
class Error(val message: String) : ReplCheckResult()
|
||||
}
|
||||
|
||||
sealed class ReplCompileResult : Serializable {
|
||||
class CompiledClasses(val classes: List<CompiledClassData>, val hasResult: Boolean) : ReplCompileResult()
|
||||
object Incomplete : ReplCompileResult()
|
||||
class HistoryMismatch(val lineNo: Int): ReplCompileResult()
|
||||
class Error(val message: String) : ReplCompileResult()
|
||||
}
|
||||
|
||||
sealed class ReplEvalResult : Serializable {
|
||||
class ValueResult(val value: Any?) : ReplEvalResult()
|
||||
object UnitResult : ReplEvalResult()
|
||||
object Incomplete : ReplEvalResult()
|
||||
class HistoryMismatch(val lineNo: Int): ReplEvalResult()
|
||||
sealed class Error(val message: String) : ReplEvalResult() {
|
||||
class Runtime(message: String) : Error(message)
|
||||
class CompileTime(message: String) : Error(message)
|
||||
}
|
||||
}
|
||||
|
||||
interface ReplChecker {
|
||||
fun check(codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>): ReplCheckResult
|
||||
}
|
||||
|
||||
interface ReplCompiler : ReplChecker {
|
||||
fun compile(codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>): ReplCompileResult
|
||||
|
||||
// a callback
|
||||
fun dependenciesAdded(classpath: List<File>)
|
||||
}
|
||||
|
||||
interface ReplCompiledEvaluator {
|
||||
|
||||
fun eval(codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>, compiledClasses: List<CompiledClassData>, hasResult: Boolean): ReplEvalResult
|
||||
|
||||
// override to capture output
|
||||
fun<T> evalWithIO(body: () -> T): T = body()
|
||||
}
|
||||
|
||||
|
||||
interface ReplEvaluator : ReplChecker {
|
||||
|
||||
fun eval(codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>): ReplEvalResult
|
||||
|
||||
fun dependenciesAdded(classpath: List<File>)
|
||||
|
||||
// override to capture output
|
||||
fun<T> evalWithIO(body: () -> T): T = body()
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.cli.common.repl;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName;
|
||||
import org.jetbrains.org.objectweb.asm.ClassReader;
|
||||
import org.jetbrains.org.objectweb.asm.util.TraceClassVisitor;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Map;
|
||||
|
||||
public class ReplClassLoader extends ClassLoader {
|
||||
|
||||
private final Map<JvmClassName, byte[]> classes = Maps.newLinkedHashMap();
|
||||
|
||||
public ReplClassLoader(@NotNull ClassLoader parent) {
|
||||
super(parent);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected Class<?> findClass(@NotNull String name) throws ClassNotFoundException {
|
||||
byte[] classBytes = classes.get(JvmClassName.byFqNameWithoutInnerClasses(name));
|
||||
if (classBytes != null) {
|
||||
return defineClass(name, classBytes, 0, classBytes.length);
|
||||
}
|
||||
else {
|
||||
return super.findClass(name);
|
||||
}
|
||||
}
|
||||
|
||||
public void addClass(@NotNull JvmClassName className, @NotNull byte[] bytes) {
|
||||
byte[] oldBytes = classes.put(className, bytes);
|
||||
if (oldBytes != null) {
|
||||
throw new IllegalStateException("Rewrite at key " + className);
|
||||
}
|
||||
}
|
||||
|
||||
public void dumpClasses(@NotNull PrintWriter writer) {
|
||||
for (byte[] classBytes : classes.values()) {
|
||||
new ClassReader(classBytes).accept(new TraceClassVisitor(writer), 0);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.cli.common.repl
|
||||
|
||||
import com.google.common.base.Throwables
|
||||
|
||||
fun <T> checkAndUpdateReplHistoryCollection(col: MutableList<Pair<ReplCodeLine, T>>, baseHistory: Iterable<ReplCodeLine>): Int? {
|
||||
val baseHistoryIt = baseHistory.iterator()
|
||||
var idx = 0
|
||||
while (baseHistoryIt.hasNext()) {
|
||||
val curLine = baseHistoryIt.next()
|
||||
if (col[idx].first != curLine) return curLine.no
|
||||
idx += 1
|
||||
}
|
||||
col.dropLast(col.size - idx)
|
||||
return null
|
||||
}
|
||||
|
||||
fun renderReplStackTrace(cause: Throwable, startFromMethodName: String): String {
|
||||
val newTrace = arrayListOf<StackTraceElement>()
|
||||
var skip = true
|
||||
for ((i, element) in cause.stackTrace.withIndex().reversed()) {
|
||||
if ("${element.className}.${element.methodName}" == startFromMethodName) {
|
||||
skip = false
|
||||
}
|
||||
if (!skip) {
|
||||
newTrace.add(element)
|
||||
}
|
||||
}
|
||||
|
||||
val resultingTrace = newTrace.reversed().dropLast(1)
|
||||
|
||||
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN", "UsePropertyAccessSyntax")
|
||||
(cause as java.lang.Throwable).setStackTrace(resultingTrace.toTypedArray())
|
||||
|
||||
return Throwables.getStackTraceAsString(cause)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user