init research
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
## :plugins:expressions-converter
|
||||
|
||||
This Kotlin Compiler plugin, used by [:core](../../core), can extract intermediate
|
||||
DataFrame expressions from `@TransformDataFrameExpressions` annotated functions.
|
||||
|
||||
It is used to generate sample "explainer dataframe" HTML files that can be used as iFrames on the documentation website.
|
||||
|
||||
Annotated functions in [core/.../test/.../samples/api](../../core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/samples/api)
|
||||
are tested, generated, and copied over to [docs/StardustDocs/resources/snippets](../../docs/StardustDocs/resources/snippets) by
|
||||
our "explainer" [plugin callback proxy](../../core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/explainer),
|
||||
which hooks into [the TestBase class](../../core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/samples/api/TestBase.kt) and
|
||||
retrieves the intermediate DataFrame expressions thanks to this module.
|
||||
@@ -0,0 +1,70 @@
|
||||
plugins {
|
||||
with(convention.plugins) {
|
||||
alias(kotlinJvm8)
|
||||
}
|
||||
with(libs.plugins) {
|
||||
alias(shadow)
|
||||
alias(publisher)
|
||||
}
|
||||
}
|
||||
|
||||
group = "org.jetbrains.kotlinx.dataframe"
|
||||
|
||||
dependencies {
|
||||
compileOnly(libs.kotlin.compiler)
|
||||
|
||||
testImplementation(libs.kotlin.compiler)
|
||||
testImplementation(libs.kotlin.compiler.internal.test.framework)
|
||||
|
||||
testRuntimeOnly(projects.core)
|
||||
|
||||
testRuntimeOnly(libs.kotlin.test)
|
||||
testRuntimeOnly(libs.kotlin.script.runtime)
|
||||
testRuntimeOnly(libs.kotlin.annotations.jvm)
|
||||
|
||||
testImplementation(platform(libs.junit.bom))
|
||||
testImplementation(libs.junit.jupiter)
|
||||
testImplementation(libs.junit.platform.commons)
|
||||
testImplementation(libs.junit.platform.launcher)
|
||||
testImplementation(libs.junit.platform.runner)
|
||||
testImplementation(libs.junit.platform.suite.api)
|
||||
|
||||
testRuntimeOnly(libs.junit.jupiter.engine)
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
useJUnitPlatform()
|
||||
doFirst {
|
||||
setLibraryProperty("org.jetbrains.kotlin.test.kotlin-stdlib", "kotlin-stdlib")
|
||||
setLibraryProperty("org.jetbrains.kotlin.test.kotlin-reflect", "kotlin-reflect")
|
||||
setLibraryProperty("org.jetbrains.kotlin.test.kotlin-test", "kotlin-test")
|
||||
setLibraryProperty("org.jetbrains.kotlin.test.kotlin-script-runtime", "kotlin-script-runtime")
|
||||
setLibraryProperty("org.jetbrains.kotlin.test.kotlin-annotations-jvm", "kotlin-annotations-jvm")
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java.setSrcDirs(listOf("src"))
|
||||
resources.setSrcDirs(listOf("resources"))
|
||||
}
|
||||
test {
|
||||
java.setSrcDirs(listOf("tests", "tests-gen"))
|
||||
resources.setSrcDirs(listOf("testResources"))
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register<JavaExec>("generateTests") {
|
||||
classpath = sourceSets.test.get().runtimeClasspath
|
||||
mainClass = "org.jetbrains.kotlinx.dataframe.GenerateTestsKt"
|
||||
}
|
||||
|
||||
fun Test.setLibraryProperty(propName: String, jarName: String) {
|
||||
val path = project.configurations
|
||||
.testRuntimeClasspath.get()
|
||||
.files
|
||||
.find { """$jarName-\d.*jar""".toRegex().matches(it.name) }
|
||||
?.absolutePath
|
||||
?: return
|
||||
systemProperty(propName, path)
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
org.jetbrains.kotlinx.dataframe.ExplainerComponentRegistrar
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package org.jetbrains.kotlinx.dataframe
|
||||
|
||||
import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
|
||||
import org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar
|
||||
import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi
|
||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||
|
||||
@OptIn(ExperimentalCompilerApi::class)
|
||||
public class ExplainerComponentRegistrar : CompilerPluginRegistrar() {
|
||||
override val supportsK2: Boolean
|
||||
get() = true
|
||||
|
||||
override val pluginId: String = "org.jetbrains.kotlinx.dataframe.ExplainerComponentRegistrar"
|
||||
|
||||
override fun ExtensionStorage.registerExtensions(configuration: CompilerConfiguration) {
|
||||
IrGenerationExtension.registerExtension(ExplainerIrGenerationExtension())
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package org.jetbrains.kotlinx.dataframe
|
||||
|
||||
import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
|
||||
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
|
||||
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
|
||||
|
||||
public class ExplainerIrGenerationExtension : IrGenerationExtension {
|
||||
override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) {
|
||||
val lowering = ExplainerIrTransformer(pluginContext)
|
||||
for (file in moduleFragment.files) {
|
||||
lowering.lower(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
+330
@@ -0,0 +1,330 @@
|
||||
@file:Suppress("ktlint:standard:no-unused-imports")
|
||||
|
||||
package org.jetbrains.kotlinx.dataframe
|
||||
|
||||
import org.jetbrains.kotlin.backend.common.FileLoweringPass
|
||||
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
|
||||
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
|
||||
import org.jetbrains.kotlin.descriptors.Modality
|
||||
import org.jetbrains.kotlin.ir.IrElement
|
||||
import org.jetbrains.kotlin.ir.IrElementBase
|
||||
import org.jetbrains.kotlin.ir.IrStatement
|
||||
import org.jetbrains.kotlin.ir.declarations.IrClass
|
||||
import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin
|
||||
import org.jetbrains.kotlin.ir.declarations.IrField
|
||||
import org.jetbrains.kotlin.ir.declarations.IrFile
|
||||
import org.jetbrains.kotlin.ir.declarations.IrFunction
|
||||
import org.jetbrains.kotlin.ir.declarations.IrParameterKind
|
||||
import org.jetbrains.kotlin.ir.declarations.path
|
||||
import org.jetbrains.kotlin.ir.expressions.IrBlockBody
|
||||
import org.jetbrains.kotlin.ir.expressions.IrBody
|
||||
import org.jetbrains.kotlin.ir.expressions.IrCall
|
||||
import org.jetbrains.kotlin.ir.expressions.IrDeclarationReference
|
||||
import org.jetbrains.kotlin.ir.expressions.IrExpression
|
||||
import org.jetbrains.kotlin.ir.expressions.IrExpressionBody
|
||||
import org.jetbrains.kotlin.ir.expressions.IrGetValue
|
||||
import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImplWithShape
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrFunctionExpressionImpl
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrGetObjectValueImpl
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrGetValueImpl
|
||||
import org.jetbrains.kotlin.ir.symbols.UnsafeDuringIrConstructionAPI
|
||||
import org.jetbrains.kotlin.ir.symbols.impl.IrSimpleFunctionSymbolImpl
|
||||
import org.jetbrains.kotlin.ir.symbols.impl.IrValueParameterSymbolImpl
|
||||
import org.jetbrains.kotlin.ir.types.classFqName
|
||||
import org.jetbrains.kotlin.ir.types.makeNullable
|
||||
import org.jetbrains.kotlin.ir.types.typeWith
|
||||
import org.jetbrains.kotlin.ir.util.SetDeclarationsParentVisitor
|
||||
import org.jetbrains.kotlin.ir.util.defaultType
|
||||
import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable
|
||||
import org.jetbrains.kotlin.ir.util.isLocal
|
||||
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
|
||||
import org.jetbrains.kotlin.ir.visitors.IrTransformer
|
||||
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
|
||||
import org.jetbrains.kotlin.name.CallableId
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import java.io.File
|
||||
|
||||
public data class ContainingDeclarations(val clazz: IrClass?, val function: IrFunction?, val statementIndex: Int = 0)
|
||||
|
||||
@OptIn(UnsafeDuringIrConstructionAPI::class)
|
||||
public class ExplainerIrTransformer(public val pluginContext: IrPluginContext) :
|
||||
IrTransformer<ContainingDeclarations>(),
|
||||
FileLoweringPass {
|
||||
public lateinit var file: IrFile
|
||||
public lateinit var source: String
|
||||
|
||||
override fun lower(irFile: IrFile) {
|
||||
var file: File
|
||||
file = File("testData/box/${irFile.path}")
|
||||
if (!file.exists()) {
|
||||
file = File(irFile.path)
|
||||
}
|
||||
this.file = irFile
|
||||
source = file.readText()
|
||||
irFile.transformChildren(this, ContainingDeclarations(null, null))
|
||||
irFile.transformChildrenVoid(object : IrElementTransformerVoid() {
|
||||
override fun visitFunction(declaration: IrFunction): IrStatement {
|
||||
declaration.acceptChildren(SetDeclarationsParentVisitor, declaration)
|
||||
return super.visitFunction(declaration)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun visitBlockBody(body: IrBlockBody, data: ContainingDeclarations): IrBody {
|
||||
for (i in 0 until body.statements.size) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
body.statements.set(
|
||||
index = i,
|
||||
element = (body.statements[i] as IrElementBase)
|
||||
.transform(
|
||||
transformer = this,
|
||||
data = data.copy(statementIndex = i),
|
||||
) as IrStatement,
|
||||
)
|
||||
}
|
||||
return body
|
||||
}
|
||||
|
||||
override fun visitClass(declaration: IrClass, data: ContainingDeclarations): IrStatement =
|
||||
super.visitClass(declaration, data.copy(clazz = declaration))
|
||||
|
||||
override fun visitFunction(declaration: IrFunction, data: ContainingDeclarations): IrStatement {
|
||||
val annotated = declaration.annotations.any {
|
||||
it.type.classFqName
|
||||
?.shortName()
|
||||
?.identifierOrNullIfSpecial
|
||||
?.equals("TransformDataFrameExpressions") == true
|
||||
}
|
||||
return if (annotated) {
|
||||
super.visitFunction(declaration, data.copy(function = declaration))
|
||||
} else {
|
||||
declaration
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitElement(element: IrElement, data: ContainingDeclarations): IrElement {
|
||||
element.transformChildren(this, data)
|
||||
return element
|
||||
}
|
||||
|
||||
override fun visitField(declaration: IrField, data: ContainingDeclarations): IrStatement {
|
||||
if (declaration.isLocal) {
|
||||
declaration.transformChildren(this, data)
|
||||
}
|
||||
return declaration
|
||||
}
|
||||
|
||||
override fun visitExpressionBody(body: IrExpressionBody, data: ContainingDeclarations): IrBody = body
|
||||
|
||||
public val dataFrameLike: Set<FqName> = setOf(
|
||||
FqName("org.jetbrains.kotlinx.dataframe.api.Pivot"),
|
||||
FqName("org.jetbrains.kotlinx.dataframe.api.ReducedPivot"),
|
||||
FqName("org.jetbrains.kotlinx.dataframe.api.PivotGroupBy"),
|
||||
FqName("org.jetbrains.kotlinx.dataframe.api.ReducedPivotGroupBy"),
|
||||
FqName("org.jetbrains.kotlinx.dataframe.api.SplitWithTransform"),
|
||||
FqName("org.jetbrains.kotlinx.dataframe.api.Merge"),
|
||||
FqName("org.jetbrains.kotlinx.dataframe.api.Split"),
|
||||
FqName("org.jetbrains.kotlinx.dataframe.api.Gather"),
|
||||
FqName("org.jetbrains.kotlinx.dataframe.api.Update"),
|
||||
FqName("org.jetbrains.kotlinx.dataframe.api.Convert"),
|
||||
FqName("org.jetbrains.kotlinx.dataframe.api.FormattedFrame"),
|
||||
FqName("org.jetbrains.kotlinx.dataframe.api.GroupBy"),
|
||||
FqName("org.jetbrains.kotlinx.dataframe.DataFrame"),
|
||||
FqName("org.jetbrains.kotlinx.dataframe.DataRow"),
|
||||
)
|
||||
|
||||
public val explainerPackage: FqName = FqName("org.jetbrains.kotlinx.dataframe.explainer")
|
||||
|
||||
override fun visitGetValue(expression: IrGetValue, data: ContainingDeclarations): IrExpression {
|
||||
if (expression.startOffset < 0) return expression
|
||||
if (expression.type.classFqName in dataFrameLike) {
|
||||
return transformDataFrameExpression(expression, expression.symbol.owner.name, receiver = null, data)
|
||||
}
|
||||
return super.visitExpression(expression, data)
|
||||
}
|
||||
|
||||
// also, what if expression type is not DataFrame, but Unit? and receiver expression is DataFrame at some point
|
||||
override fun visitCall(expression: IrCall, data: ContainingDeclarations): IrExpression {
|
||||
if (expression.startOffset < 0) return expression
|
||||
if (expression.type.classFqName in dataFrameLike) {
|
||||
if (expression.symbol.owner.name == Name.identifier("component1")) return expression
|
||||
val extensionReceiverIndex =
|
||||
expression.symbol.owner.parameters.indexOfFirst { it.kind == IrParameterKind.ExtensionReceiver }
|
||||
var receiver: IrExpression?
|
||||
// expression.arguments[extensionReceiverIndex] = extension callables,
|
||||
// expression.dispatchReceiver = member callables such as "GroupBy.aggregate"
|
||||
if (extensionReceiverIndex >= 0) {
|
||||
receiver = expression.arguments[extensionReceiverIndex]!!
|
||||
val transformedExtensionReceiver = receiver.transform(this, data)
|
||||
expression.arguments[extensionReceiverIndex] = transformedExtensionReceiver
|
||||
} else {
|
||||
receiver = expression.dispatchReceiver
|
||||
val transformedExtensionReceiver = expression.dispatchReceiver?.transform(this, data)
|
||||
expression.dispatchReceiver = transformedExtensionReceiver
|
||||
}
|
||||
|
||||
return transformDataFrameExpression(expression, expression.symbol.owner.name, receiver = receiver, data)
|
||||
}
|
||||
return super.visitExpression(expression, data)
|
||||
}
|
||||
|
||||
private fun transformDataFrameExpression(
|
||||
expression: IrDeclarationReference,
|
||||
ownerName: Name,
|
||||
receiver: IrExpression?,
|
||||
data: ContainingDeclarations,
|
||||
): IrCall {
|
||||
val alsoReference = pluginContext
|
||||
.referenceFunctions(
|
||||
CallableId(FqName("kotlin"), Name.identifier("also")),
|
||||
).single()
|
||||
|
||||
val result = IrCallImplWithShape(
|
||||
startOffset = -1,
|
||||
endOffset = -1,
|
||||
type = expression.type,
|
||||
symbol = alsoReference,
|
||||
typeArgumentsCount = 1,
|
||||
valueArgumentsCount = 1,
|
||||
contextParameterCount = 0,
|
||||
hasDispatchReceiver = true,
|
||||
hasExtensionReceiver = true,
|
||||
).apply {
|
||||
val extensionReceiverIndex =
|
||||
this.symbol.owner.parameters.indexOfFirst { it.kind == IrParameterKind.ExtensionReceiver }
|
||||
if (extensionReceiverIndex >= 0) {
|
||||
this.arguments[extensionReceiverIndex] = expression
|
||||
} else {
|
||||
this.insertExtensionReceiver(expression)
|
||||
}
|
||||
|
||||
typeArguments[0] = expression.type
|
||||
|
||||
val symbol = IrSimpleFunctionSymbolImpl()
|
||||
val alsoLambda = pluginContext.irFactory
|
||||
.createSimpleFunction(
|
||||
startOffset = -1,
|
||||
endOffset = -1,
|
||||
origin = IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA,
|
||||
symbol = symbol,
|
||||
name = Name.special("<anonymous>"),
|
||||
visibility = DescriptorVisibilities.LOCAL,
|
||||
modality = Modality.FINAL,
|
||||
returnType = pluginContext.irBuiltIns.unitType,
|
||||
isInline = false,
|
||||
isExternal = false,
|
||||
isTailrec = false,
|
||||
isSuspend = false,
|
||||
isOperator = false,
|
||||
isInfix = false,
|
||||
isExpect = false,
|
||||
).apply {
|
||||
// replace all regular value parameters with a single one `it`
|
||||
parameters = parameters.filterNot { it.kind == IrParameterKind.Regular } +
|
||||
pluginContext.irFactory.createValueParameter(
|
||||
startOffset = -1,
|
||||
endOffset = -1,
|
||||
origin = IrDeclarationOrigin.DEFINED,
|
||||
kind = IrParameterKind.Regular,
|
||||
name = Name.identifier("it"),
|
||||
type = expression.type,
|
||||
isAssignable = false,
|
||||
symbol = IrValueParameterSymbolImpl(),
|
||||
varargElementType = null,
|
||||
isCrossinline = false,
|
||||
isNoinline = false,
|
||||
isHidden = false,
|
||||
)
|
||||
|
||||
val itSymbol = parameters.first { it.kind == IrParameterKind.Regular }.symbol
|
||||
val source = try {
|
||||
source.substring(expression.startOffset, expression.endOffset)
|
||||
} catch (e: Exception) {
|
||||
throw Exception("$expression ${ownerName.asString()} $source", e)
|
||||
}
|
||||
val expressionId = expressionId(expression)
|
||||
val receiverId = receiver?.let { expressionId(it) }
|
||||
val valueArguments = buildList<IrExpression?> {
|
||||
add(source.irConstImpl()) // source: String
|
||||
add(ownerName.asStringStripSpecialMarkers().irConstImpl()) // name: String
|
||||
add(IrGetValueImpl(-1, -1, itSymbol)) // df: Any
|
||||
add(expressionId.irConstImpl()) // id: String
|
||||
add(receiverId.irConstImpl()) // receiverId: String?
|
||||
add(data.clazz?.fqNameWhenAvailable?.asString().irConstImpl()) // containingClassFqName: String?
|
||||
add(data.function?.name?.asString().irConstImpl()) // containingFunName: String?
|
||||
add(
|
||||
IrConstImpl.int(
|
||||
-1,
|
||||
-1,
|
||||
pluginContext.irBuiltIns.intType,
|
||||
data.statementIndex,
|
||||
),
|
||||
) // statementIndex: Int
|
||||
}
|
||||
body = pluginContext.irFactory.createBlockBody(-1, -1).apply {
|
||||
val callableId = CallableId(
|
||||
explainerPackage,
|
||||
FqName("PluginCallbackProxy"),
|
||||
Name.identifier("doAction"),
|
||||
)
|
||||
val doAction = pluginContext.referenceFunctions(callableId).single()
|
||||
statements += IrCallImplWithShape(
|
||||
startOffset = -1,
|
||||
endOffset = -1,
|
||||
type = doAction.owner.returnType,
|
||||
symbol = doAction,
|
||||
typeArgumentsCount = 0,
|
||||
valueArgumentsCount = valueArguments.size,
|
||||
contextParameterCount = 0,
|
||||
hasDispatchReceiver = true,
|
||||
hasExtensionReceiver = false,
|
||||
).apply {
|
||||
val clazz = ClassId(explainerPackage, Name.identifier("PluginCallbackProxy"))
|
||||
val plugin = pluginContext.referenceClass(clazz)!!
|
||||
val pluginType = plugin.owner.defaultType
|
||||
dispatchReceiver = IrGetObjectValueImpl(-1, -1, pluginType, plugin)
|
||||
val firstValueArgumentIndex = 1 // skipping dispatch receiver
|
||||
valueArguments.forEachIndexed { i, argument ->
|
||||
this.arguments[firstValueArgumentIndex + i] = argument
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
val alsoLambdaExpression = IrFunctionExpressionImpl(
|
||||
startOffset = -1,
|
||||
endOffset = -1,
|
||||
type = pluginContext.irBuiltIns
|
||||
.functionN(2)
|
||||
.typeWith(listOf(expression.type, pluginContext.irBuiltIns.unitType)),
|
||||
function = alsoLambda,
|
||||
origin = IrStatementOrigin.LAMBDA,
|
||||
)
|
||||
|
||||
val firstValueArgumentIndex = this.symbol.owner.parameters
|
||||
.indexOfFirst { it.kind == IrParameterKind.Regular }
|
||||
.takeUnless { it < 0 } ?: this.symbol.owner.parameters.size
|
||||
this.arguments[firstValueArgumentIndex] = alsoLambdaExpression
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun String?.irConstImpl(): IrConstImpl {
|
||||
val nullableString = pluginContext.irBuiltIns.stringType.makeNullable()
|
||||
val argument = if (this == null) {
|
||||
IrConstImpl.constNull(-1, -1, nullableString)
|
||||
} else {
|
||||
IrConstImpl.string(-1, -1, nullableString, this)
|
||||
}
|
||||
return argument
|
||||
}
|
||||
|
||||
private fun expressionId(expression: IrExpression): String {
|
||||
val line = file.fileEntry.getLineNumber(expression.startOffset)
|
||||
val column = file.fileEntry.getColumnNumber(expression.startOffset)
|
||||
return "${file.path}:${line + 1}:${column + 1}"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.jetbrains.kotlinx.dataframe.explainer
|
||||
|
||||
import org.jetbrains.kotlinx.dataframe.*
|
||||
import org.jetbrains.kotlinx.dataframe.api.*
|
||||
|
||||
object PluginCallback {
|
||||
fun doAction(any: Any) {}
|
||||
}
|
||||
|
||||
fun box(): String {
|
||||
val a: GroupBy<*, *> = dataFrameOf("a")(1).groupBy("a")
|
||||
a.also {
|
||||
PluginCallback.doAction(it)
|
||||
}
|
||||
return "OK"
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package
|
||||
|
||||
package org {
|
||||
|
||||
package org.jetbrains {
|
||||
|
||||
package org.jetbrains.kotlinx {
|
||||
|
||||
package org.jetbrains.kotlinx.dataframe {
|
||||
|
||||
package org.jetbrains.kotlinx.dataframe.explainer {
|
||||
public fun box(): kotlin.String
|
||||
|
||||
public object PluginCallback {
|
||||
private constructor PluginCallback()
|
||||
public final fun doAction(/*0*/ any: kotlin.Any): kotlin.Unit
|
||||
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
package org.jetbrains.kotlinx.dataframe.explainer
|
||||
|
||||
import org.jetbrains.kotlinx.dataframe.*
|
||||
import org.jetbrains.kotlinx.dataframe.api.*
|
||||
|
||||
@TransformDataFrameExpressions
|
||||
fun callChain(df: DataFrame<*>) {
|
||||
val df1 = df
|
||||
.filter { "age"<Int>() > 20 }
|
||||
.groupBy("value")
|
||||
.sum()
|
||||
}
|
||||
|
||||
interface Person
|
||||
|
||||
class Wrapper {
|
||||
val df = dataFrameOf("name", "age", "city", "weight")(
|
||||
"Alice", 15, "London", 54,
|
||||
"Bob", 45, "Dubai", 87,
|
||||
"Charlie", 20, "Moscow", null,
|
||||
"Charlie", 40, "Milan", null,
|
||||
"Bob", 30, "Tokyo", 68,
|
||||
"Alice", 20, null, 55,
|
||||
"Charlie", 30, "Moscow", 90
|
||||
)
|
||||
|
||||
val typed: DataFrame<Person> = df.cast()
|
||||
|
||||
@TransformDataFrameExpressions
|
||||
fun ff() {
|
||||
val name by column<String>()
|
||||
val aggregated = typed.groupBy { name() }.aggregate {
|
||||
(if (name().first().startsWith("A")) first() else null) into "agg"
|
||||
}["agg"]
|
||||
}
|
||||
}
|
||||
|
||||
@TransformDataFrameExpressions
|
||||
fun aggregateDf() {
|
||||
val df = dataFrameOf("firstName", "lastName", "age", "city", "weight", "isHappy")(
|
||||
"Alice", "Cooper", 15, "London", 54, true,
|
||||
"Bob", "Dylan", 45, "Dubai", 87, true,
|
||||
"Charlie", "Daniels", 20, "Moscow", null, false,
|
||||
"Charlie", "Chaplin", 40, "Milan", null, true,
|
||||
"Bob", "Marley", 30, "Tokyo", 68, true,
|
||||
"Alice", "Wolf", 20, null, 55, false,
|
||||
"Charlie", "Byrd", 30, "Moscow", 90, true
|
||||
).group("firstName", "lastName").into("name")
|
||||
|
||||
df.groupBy("city").aggregate {
|
||||
count() into "total"
|
||||
count { "age"<Int>() > 18 } into "adults"
|
||||
median("age") into "median age"
|
||||
min("age") into "min age"
|
||||
maxBy("age")["name"] into "oldest"
|
||||
}
|
||||
}
|
||||
|
||||
@TransformDataFrameExpressions
|
||||
fun move() {
|
||||
val df = dataFrameOf("firstName", "lastName", "age", "city", "weight", "isHappy")(
|
||||
"Alice", "Cooper", 15, "London", 54, true,
|
||||
"Bob", "Dylan", 45, "Dubai", 87, true,
|
||||
"Charlie", "Daniels", 20, "Moscow", null, false,
|
||||
"Charlie", "Chaplin", 40, "Milan", null, true,
|
||||
"Bob", "Marley", 30, "Tokyo", 68, true,
|
||||
"Alice", "Wolf", 20, null, 55, false,
|
||||
"Charlie", "Byrd", 30, "Moscow", 90, true
|
||||
).group("firstName", "lastName").into("name")
|
||||
|
||||
df.move("age", "weight").into { pathOf("info", it.name()) }
|
||||
}
|
||||
|
||||
fun interface PluginCallback {
|
||||
fun doAction(
|
||||
source: String,
|
||||
name: String,
|
||||
df: Any,
|
||||
id: String,
|
||||
receiverId: String?,
|
||||
containingClassFqName: String?,
|
||||
containingFunName: String?,
|
||||
statementIndex: Int
|
||||
)
|
||||
}
|
||||
|
||||
object PluginCallbackProxy : PluginCallback {
|
||||
|
||||
var action: PluginCallback = PluginCallback { _, _, _, _, _, _, _, _ -> Unit }
|
||||
|
||||
override fun doAction(source: String, name: String, df: Any, id: String, receiverId: String?, containingClassFqName: String?, containingFunName: String?, statemenIndex: Int) {
|
||||
action.doAction(source, name, df, id, receiverId, containingClassFqName, containingFunName, statemenIndex)
|
||||
}
|
||||
}
|
||||
|
||||
//fun callChainTransformed(df: DataFrame<*>) {
|
||||
// val df1 = df
|
||||
// .filter { "age"<Int>() > 20 }
|
||||
// .also { PluginCallbackProxy.doAction(""".filter { "age"<Int>() > 20 }""", "filter", it) }
|
||||
//}
|
||||
|
||||
//fun callChainTransformed(df: DataFrame<*>) {
|
||||
// val df1 = df
|
||||
// .filter { "age"<Int>() > 20 }
|
||||
// .also { PluginCallbackProxy.action(""".filter { "age"<Int>() > 20 }""", it) }
|
||||
// .groupBy("something")
|
||||
// .sum()
|
||||
// .also { PluginCallbackProxy.action(""".groupBy("something").sum()""", it) }
|
||||
//}
|
||||
|
||||
annotation class TransformDataFrameExpressions
|
||||
|
||||
fun box(): String {
|
||||
val age by columnOf(10, 21, 30, 1)
|
||||
val value by columnOf("a", "b", "c", "c")
|
||||
val df = dataFrameOf(age, value)
|
||||
val expressions = mutableListOf<String>()
|
||||
PluginCallbackProxy.action = PluginCallback { source, _, df, id, receiverId, containingClassFqName, containingFunName, statementIndex ->
|
||||
println("== Call ==")
|
||||
expressions += source
|
||||
if (df is AnyFrame) {
|
||||
println(source)
|
||||
df.print()
|
||||
println(id)
|
||||
println(receiverId)
|
||||
println(containingClassFqName)
|
||||
println(containingFunName)
|
||||
println("statementIndex = ${statementIndex}")
|
||||
} else {
|
||||
println(df::class)
|
||||
}
|
||||
println("== End ==")
|
||||
}
|
||||
|
||||
println("CallChain")
|
||||
callChain(df)
|
||||
println("expressions = ${expressions}")
|
||||
expressions.clear()
|
||||
|
||||
println("ff")
|
||||
Wrapper().ff()
|
||||
println("expressions = ${expressions}")
|
||||
expressions.clear()
|
||||
// callChainTransformed(df)
|
||||
|
||||
println("aggregateDf")
|
||||
aggregateDf()
|
||||
println("expressions = ${expressions}")
|
||||
expressions.clear()
|
||||
|
||||
println("move")
|
||||
move()
|
||||
println("expressions = ${expressions}")
|
||||
expressions.clear()
|
||||
return "OK"
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package
|
||||
|
||||
package org {
|
||||
|
||||
package org.jetbrains {
|
||||
|
||||
package org.jetbrains.kotlinx {
|
||||
|
||||
package org.jetbrains.kotlinx.dataframe {
|
||||
|
||||
package org.jetbrains.kotlinx.dataframe.explainer {
|
||||
@org.jetbrains.kotlinx.dataframe.explainer.TransformDataFrameExpressions public fun aggregateDf(): kotlin.Unit
|
||||
public fun box(): kotlin.String
|
||||
@org.jetbrains.kotlinx.dataframe.explainer.TransformDataFrameExpressions public fun callChain(/*0*/ df: org.jetbrains.kotlinx.dataframe.DataFrame<*>): kotlin.Unit
|
||||
@org.jetbrains.kotlinx.dataframe.explainer.TransformDataFrameExpressions public fun move(): kotlin.Unit
|
||||
|
||||
public interface Person {
|
||||
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
}
|
||||
|
||||
public fun interface PluginCallback {
|
||||
public abstract fun doAction(/*0*/ source: kotlin.String, /*1*/ name: kotlin.String, /*2*/ df: kotlin.Any, /*3*/ id: kotlin.String, /*4*/ receiverId: kotlin.String?, /*5*/ containingClassFqName: kotlin.String?, /*6*/ containingFunName: kotlin.String?, /*7*/ statementIndex: kotlin.Int): kotlin.Unit
|
||||
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
}
|
||||
|
||||
public object PluginCallbackProxy : org.jetbrains.kotlinx.dataframe.explainer.PluginCallback {
|
||||
private constructor PluginCallbackProxy()
|
||||
public final var action: org.jetbrains.kotlinx.dataframe.explainer.PluginCallback
|
||||
public open override /*1*/ fun doAction(/*0*/ source: kotlin.String, /*1*/ name: kotlin.String, /*2*/ df: kotlin.Any, /*3*/ id: kotlin.String, /*4*/ receiverId: kotlin.String?, /*5*/ containingClassFqName: kotlin.String?, /*6*/ containingFunName: kotlin.String?, /*7*/ statemenIndex: kotlin.Int): kotlin.Unit
|
||||
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
}
|
||||
|
||||
public final annotation class TransformDataFrameExpressions : kotlin.Annotation {
|
||||
public constructor TransformDataFrameExpressions()
|
||||
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
}
|
||||
|
||||
public final class Wrapper {
|
||||
public constructor Wrapper()
|
||||
public final val df: org.jetbrains.kotlinx.dataframe.DataFrame<*>
|
||||
public final val typed: org.jetbrains.kotlinx.dataframe.DataFrame<org.jetbrains.kotlinx.dataframe.explainer.Person>
|
||||
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
@org.jetbrains.kotlinx.dataframe.explainer.TransformDataFrameExpressions public final fun ff(): kotlin.Unit
|
||||
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package org.jetbrains.kotlinx.dataframe.explainer
|
||||
|
||||
fun print(any: Any) {}
|
||||
|
||||
fun printM(pair: Pair<String, Any>) {
|
||||
val (text, obj) = pair
|
||||
println("# ${text}: $obj")
|
||||
}
|
||||
|
||||
fun ir() {
|
||||
val df = Any()
|
||||
printM("Any()" to df)
|
||||
}
|
||||
|
||||
fun box(): String {
|
||||
val df = Any()
|
||||
val df1 = 1 + 2
|
||||
print(df)
|
||||
print(df1)
|
||||
return "OK"
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package
|
||||
|
||||
package org {
|
||||
|
||||
package org.jetbrains {
|
||||
|
||||
package org.jetbrains.kotlinx {
|
||||
|
||||
package org.jetbrains.kotlinx.dataframe {
|
||||
|
||||
package org.jetbrains.kotlinx.dataframe.explainer {
|
||||
public fun box(): kotlin.String
|
||||
public fun ir(): kotlin.Unit
|
||||
public fun print(/*0*/ any: kotlin.Any): kotlin.Unit
|
||||
public fun printM(/*0*/ pair: kotlin.Pair<kotlin.String, kotlin.Any>): kotlin.Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
|
||||
|
||||
package org.jetbrains.kotlinx.dataframe;
|
||||
|
||||
import com.intellij.testFramework.TestDataPath;
|
||||
import org.jetbrains.kotlin.test.util.KtTestUtil;
|
||||
import org.jetbrains.kotlin.test.TestMetadata;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/** This class is generated by {@link org.jetbrains.kotlinx.dataframe.GenerateTestsKt}. DO NOT MODIFY MANUALLY */
|
||||
@SuppressWarnings("all")
|
||||
@TestMetadata("testData/box")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
public class ExplainerBlackBoxCodegenTestGenerated extends AbstractExplainerBlackBoxCodegenTest {
|
||||
@Test
|
||||
public void testAllFilesPresentInBox() {
|
||||
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("testData/box"), Pattern.compile("^(.+)\\.kt$"), null, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("any.kt")
|
||||
public void testAny() {
|
||||
runTest("testData/box/any.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("df.kt")
|
||||
public void testDf() {
|
||||
runTest("testData/box/df.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("test.kt")
|
||||
public void testTest() {
|
||||
runTest("testData/box/test.kt");
|
||||
}
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
package org.jetbrains.kotlinx.dataframe
|
||||
|
||||
import org.jetbrains.kotlin.config.JvmTarget
|
||||
import org.jetbrains.kotlin.platform.jvm.JvmPlatforms
|
||||
import org.jetbrains.kotlin.test.TargetBackend
|
||||
import org.jetbrains.kotlin.test.TestJdkKind
|
||||
import org.jetbrains.kotlin.test.backend.BlackBoxCodegenSuppressor
|
||||
import org.jetbrains.kotlin.test.backend.handlers.IrPrettyKotlinDumpHandler
|
||||
import org.jetbrains.kotlin.test.backend.handlers.IrTextDumpHandler
|
||||
import org.jetbrains.kotlin.test.backend.handlers.IrTreeVerifierHandler
|
||||
import org.jetbrains.kotlin.test.backend.handlers.JvmBoxRunner
|
||||
import org.jetbrains.kotlin.test.backend.ir.JvmIrBackendFacade
|
||||
import org.jetbrains.kotlin.test.builders.TestConfigurationBuilder
|
||||
import org.jetbrains.kotlin.test.builders.classicFrontendHandlersStep
|
||||
import org.jetbrains.kotlin.test.builders.irHandlersStep
|
||||
import org.jetbrains.kotlin.test.builders.jvmArtifactsHandlersStep
|
||||
import org.jetbrains.kotlin.test.builders.psi2IrStep
|
||||
import org.jetbrains.kotlin.test.directives.JvmEnvironmentConfigurationDirectives
|
||||
import org.jetbrains.kotlin.test.frontend.classic.ClassicFrontendFacade
|
||||
import org.jetbrains.kotlin.test.frontend.classic.handlers.ClassicDiagnosticsHandler
|
||||
import org.jetbrains.kotlin.test.frontend.classic.handlers.DeclarationsDumpHandler
|
||||
import org.jetbrains.kotlin.test.model.DependencyKind
|
||||
import org.jetbrains.kotlin.test.model.FrontendKinds
|
||||
import org.jetbrains.kotlin.test.model.TestModule
|
||||
import org.jetbrains.kotlin.test.services.RuntimeClasspathProvider
|
||||
import org.jetbrains.kotlin.test.services.TemporaryDirectoryManager
|
||||
import org.jetbrains.kotlin.test.services.TestServices
|
||||
import org.jetbrains.kotlin.test.services.configuration.CommonEnvironmentConfigurator
|
||||
import org.jetbrains.kotlin.test.services.configuration.JvmEnvironmentConfigurator
|
||||
import org.jetbrains.kotlinx.dataframe.services.TemporaryDirectoryManagerImplFixed
|
||||
import java.io.File
|
||||
|
||||
open class AbstractExplainerBlackBoxCodegenTest : BaseTestRunner() {
|
||||
|
||||
override fun configure(builder: TestConfigurationBuilder): Unit =
|
||||
with(builder) {
|
||||
globalDefaults {
|
||||
frontend = FrontendKinds.ClassicAndFIR
|
||||
targetPlatform = JvmPlatforms.jvm8
|
||||
dependencyKind = DependencyKind.Binary
|
||||
targetBackend = TargetBackend.JVM_IR
|
||||
}
|
||||
defaultDirectives {
|
||||
JvmEnvironmentConfigurationDirectives.JDK_KIND with TestJdkKind.FULL_JDK
|
||||
JvmEnvironmentConfigurationDirectives.JVM_TARGET with JvmTarget.JVM_1_8
|
||||
+JvmEnvironmentConfigurationDirectives.WITH_REFLECT
|
||||
}
|
||||
facadeStep(::ClassicFrontendFacade)
|
||||
commonFirWithPluginFrontendConfiguration()
|
||||
classicFrontendHandlersStep {
|
||||
useHandlers(
|
||||
::ClassicDiagnosticsHandler,
|
||||
::DeclarationsDumpHandler,
|
||||
)
|
||||
}
|
||||
psi2IrStep()
|
||||
irHandlersStep {
|
||||
useHandlers(
|
||||
::IrPrettyKotlinDumpHandler,
|
||||
::IrTextDumpHandler,
|
||||
::IrTreeVerifierHandler,
|
||||
)
|
||||
}
|
||||
facadeStep(::JvmIrBackendFacade)
|
||||
jvmArtifactsHandlersStep {
|
||||
useHandlers(::JvmBoxRunner)
|
||||
}
|
||||
useConfigurators(::JvmEnvironmentConfigurator, ::CommonEnvironmentConfigurator, ::PluginAnnotationsProvider)
|
||||
useCustomRuntimeClasspathProviders(::MyClasspathProvider)
|
||||
useAfterAnalysisCheckers(::BlackBoxCodegenSuppressor)
|
||||
useAdditionalService<TemporaryDirectoryManager>(::TemporaryDirectoryManagerImplFixed)
|
||||
}
|
||||
|
||||
class MyClasspathProvider(testServices: TestServices) : RuntimeClasspathProvider(testServices) {
|
||||
override fun runtimeClassPaths(module: TestModule): List<File> =
|
||||
(classpathFromClassloader(javaClass.classLoader) ?: error("no classpath"))
|
||||
}
|
||||
}
|
||||
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
package org.jetbrains.kotlinx.dataframe
|
||||
|
||||
import org.jetbrains.kotlin.test.initIdeaConfiguration
|
||||
import org.jetbrains.kotlin.test.runners.AbstractKotlinCompilerTest
|
||||
import org.jetbrains.kotlin.test.services.EnvironmentBasedStandardLibrariesPathProvider
|
||||
import org.jetbrains.kotlin.test.services.KotlinStandardLibrariesPathProvider
|
||||
import org.junit.jupiter.api.BeforeAll
|
||||
|
||||
abstract class BaseTestRunner : AbstractKotlinCompilerTest() {
|
||||
companion object {
|
||||
@BeforeAll
|
||||
@JvmStatic
|
||||
fun setUp() {
|
||||
initIdeaConfiguration()
|
||||
}
|
||||
}
|
||||
|
||||
override fun createKotlinStandardLibrariesPathProvider(): KotlinStandardLibrariesPathProvider =
|
||||
EnvironmentBasedStandardLibrariesPathProvider
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package org.jetbrains.kotlinx.dataframe
|
||||
|
||||
import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
|
||||
import org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar
|
||||
import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi
|
||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||
import org.jetbrains.kotlin.test.model.TestModule
|
||||
import org.jetbrains.kotlin.test.services.EnvironmentConfigurator
|
||||
import org.jetbrains.kotlin.test.services.TestServices
|
||||
|
||||
class ExtensionRegistrarConfigurator(testServices: TestServices) : EnvironmentConfigurator(testServices) {
|
||||
@OptIn(ExperimentalCompilerApi::class)
|
||||
override fun CompilerPluginRegistrar.ExtensionStorage.registerCompilerExtensions(
|
||||
module: TestModule,
|
||||
configuration: CompilerConfiguration,
|
||||
) {
|
||||
IrGenerationExtension.registerExtension(ExplainerIrGenerationExtension())
|
||||
}
|
||||
}
|
||||
Vendored
+13
@@ -0,0 +1,13 @@
|
||||
package org.jetbrains.kotlinx.dataframe
|
||||
|
||||
import org.jetbrains.kotlin.generators.dsl.junit5.generateTestGroupSuiteWithJUnit5
|
||||
|
||||
fun main() {
|
||||
generateTestGroupSuiteWithJUnit5 {
|
||||
testGroup(testDataRoot = "testData", testsRoot = "tests-gen") {
|
||||
testClass<AbstractExplainerBlackBoxCodegenTest> {
|
||||
model("box")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package org.jetbrains.kotlinx.dataframe
|
||||
|
||||
import org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoots
|
||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||
import org.jetbrains.kotlin.test.model.TestModule
|
||||
import org.jetbrains.kotlin.test.services.EnvironmentConfigurator
|
||||
import org.jetbrains.kotlin.test.services.TestServices
|
||||
|
||||
class PluginAnnotationsProvider(testServices: TestServices) : EnvironmentConfigurator(testServices) {
|
||||
override fun configureCompilerConfiguration(configuration: CompilerConfiguration, module: TestModule) {
|
||||
configuration.addJvmClasspathRoots(classpathFromClassloader(javaClass.classLoader) ?: error("no classpath"))
|
||||
}
|
||||
}
|
||||
+479
@@ -0,0 +1,479 @@
|
||||
/*
|
||||
* Copyright 2010-2018 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 org.jetbrains.kotlinx.dataframe
|
||||
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileNotFoundException
|
||||
import java.net.JarURLConnection
|
||||
import java.net.URL
|
||||
import java.net.URLClassLoader
|
||||
import java.util.jar.JarInputStream
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
// Kotlin Compiler dependencies
|
||||
internal const val KOTLIN_JAVA_STDLIB_JAR = "kotlin-stdlib.jar"
|
||||
internal const val KOTLIN_JAVA_REFLECT_JAR = "kotlin-reflect.jar"
|
||||
internal const val KOTLIN_JAVA_SCRIPT_RUNTIME_JAR = "kotlin-script-runtime.jar"
|
||||
internal const val TROVE4J_JAR = "trove4j.jar"
|
||||
internal const val KOTLIN_SCRIPTING_COMPILER_JAR = "kotlin-scripting-compiler.jar"
|
||||
internal const val KOTLIN_SCRIPTING_COMPILER_EMBEDDABLE_JAR = "kotlin-scripting-compiler-embeddable.jar"
|
||||
internal const val KOTLIN_SCRIPTING_COMPILER_IMPL_JAR = "kotlin-scripting-compiler-impl.jar"
|
||||
internal const val KOTLIN_SCRIPTING_COMPILER_IMPL_EMBEDDABLE_JAR = "kotlin-scripting-compiler-impl-embeddable.jar"
|
||||
internal const val KOTLIN_SCRIPTING_COMMON_JAR = "kotlin-scripting-common.jar"
|
||||
internal const val KOTLIN_SCRIPTING_JVM_JAR = "kotlin-scripting-jvm.jar"
|
||||
|
||||
internal const val KOTLIN_COMPILER_NAME = "kotlin-compiler"
|
||||
internal const val KOTLIN_COMPILER_JAR = "$KOTLIN_COMPILER_NAME.jar"
|
||||
|
||||
private val JAR_COLLECTIONS_CLASSES_PATHS = arrayOf("BOOT-INF/classes", "WEB-INF/classes")
|
||||
private val JAR_COLLECTIONS_LIB_PATHS = arrayOf("BOOT-INF/lib", "WEB-INF/lib")
|
||||
private val JAR_COLLECTIONS_KEY_PATHS = JAR_COLLECTIONS_CLASSES_PATHS + JAR_COLLECTIONS_LIB_PATHS
|
||||
internal const val JAR_MANIFEST_RESOURCE_NAME = "META-INF/MANIFEST.MF"
|
||||
|
||||
internal const val KOTLIN_SCRIPT_CLASSPATH_PROPERTY = "kotlin.script.classpath"
|
||||
internal const val KOTLIN_COMPILER_CLASSPATH_PROPERTY = "kotlin.compiler.classpath"
|
||||
internal const val KOTLIN_COMPILER_JAR_PROPERTY = "kotlin.compiler.jar"
|
||||
internal const val KOTLIN_STDLIB_JAR_PROPERTY = "kotlin.java.stdlib.jar"
|
||||
internal const val KOTLIN_REFLECT_JAR_PROPERTY = "kotlin.java.reflect.jar"
|
||||
|
||||
// obsolete name, but maybe still used in the wild
|
||||
// TODO: consider removing
|
||||
internal const val KOTLIN_RUNTIME_JAR_PROPERTY = "kotlin.java.runtime.jar"
|
||||
internal const val KOTLIN_SCRIPT_RUNTIME_JAR_PROPERTY = "kotlin.script.runtime.jar"
|
||||
|
||||
private val validClasspathFilesExtensions = setOf("jar", "zip", "java")
|
||||
private val validJarCollectionFilesExtensions = setOf("jar", "war", "zip")
|
||||
|
||||
class ClasspathExtractionException(message: String) : Exception(message)
|
||||
|
||||
fun classpathFromClassloader(currentClassLoader: ClassLoader, unpackJarCollections: Boolean = false): List<File>? {
|
||||
val processedJars = hashSetOf<File>()
|
||||
val unpackJarCollectionsDir by lazy {
|
||||
File.createTempFile("unpackedJarCollections", null).canonicalFile.apply {
|
||||
delete()
|
||||
mkdir()
|
||||
setReadable(false, false)
|
||||
setWritable(false, false)
|
||||
setExecutable(false, false)
|
||||
setReadable(true, true)
|
||||
setWritable(true, true)
|
||||
setExecutable(true, true)
|
||||
|
||||
Runtime.getRuntime().addShutdownHook(
|
||||
Thread {
|
||||
deleteRecursively()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
return allRelatedClassLoaders(currentClassLoader).flatMap { classLoader ->
|
||||
var classPath = emptySequence<File>()
|
||||
if (unpackJarCollections &&
|
||||
JAR_COLLECTIONS_KEY_PATHS.any { classLoader.getResource(it)?.file?.isNotEmpty() == true }
|
||||
) {
|
||||
// if cache dir is specified, find all jar collections (spring boot fat jars and WARs so far, and unpack it accordingly
|
||||
val jarCollections = JAR_COLLECTIONS_KEY_PATHS
|
||||
.asSequence()
|
||||
.flatMap { currentClassLoader.getResources(it).asSequence() }
|
||||
.mapNotNull {
|
||||
it.toContainingJarOrNull()?.takeIf { file ->
|
||||
// additionally mark/check processed collection jars since unpacking is expensive
|
||||
file.extension in validJarCollectionFilesExtensions && processedJars.add(file)
|
||||
}
|
||||
}
|
||||
classPath +=
|
||||
jarCollections
|
||||
.flatMap { it.unpackJarCollection(unpackJarCollectionsDir) }
|
||||
.filter { it.isValidClasspathFile() }
|
||||
}
|
||||
classPath += when (classLoader) {
|
||||
is URLClassLoader -> {
|
||||
classLoader.urLs.asSequence().mapNotNull { url -> url.toValidClasspathFileOrNull() }
|
||||
}
|
||||
|
||||
else -> {
|
||||
classLoader.classPathFromGetUrlsMethodOrNull()
|
||||
?: classLoader.classPathFromTypicalResourceUrls()
|
||||
}
|
||||
}
|
||||
classPath
|
||||
}.filter { processedJars.add(it) }
|
||||
.toList()
|
||||
.takeIf { it.isNotEmpty() }
|
||||
}
|
||||
|
||||
internal fun URL.toValidClasspathFileOrNull(): File? =
|
||||
(toContainingJarOrNull() ?: toFileOrNull())?.takeIf { it.isValidClasspathFile() }
|
||||
|
||||
internal fun File.isValidClasspathFile(): Boolean =
|
||||
isDirectory || (isFile && extension in validClasspathFilesExtensions)
|
||||
|
||||
private fun ClassLoader.classPathFromGetUrlsMethodOrNull(): Sequence<File>? =
|
||||
try {
|
||||
// e.g. for IDEA platform UrlClassLoader
|
||||
val getUrls = this::class.java.getMethod("getUrls")
|
||||
getUrls.isAccessible = true
|
||||
val result = getUrls.invoke(this) as? List<Any?>
|
||||
result?.asSequence()?.filterIsInstance<URL>()?.mapNotNull { it.toValidClasspathFileOrNull() }
|
||||
} catch (e: Throwable) {
|
||||
null
|
||||
}
|
||||
|
||||
internal class ClassLoaderResourceRootFIlePathCalculator(private val keyResourcePath: String) {
|
||||
private var keyResourcePathDepth = -1
|
||||
|
||||
operator fun invoke(resourceFile: File): File {
|
||||
if (keyResourcePathDepth < 0) {
|
||||
keyResourcePathDepth =
|
||||
if (keyResourcePath.isBlank()) {
|
||||
0
|
||||
} else {
|
||||
keyResourcePath.trim('/').count { it == '/' } + 1
|
||||
}
|
||||
}
|
||||
var root = resourceFile
|
||||
for (i in 0 until keyResourcePathDepth) {
|
||||
root = root.parentFile
|
||||
}
|
||||
return root
|
||||
}
|
||||
}
|
||||
|
||||
internal fun ClassLoader.rawClassPathFromKeyResourcePath(keyResourcePath: String): Sequence<File> {
|
||||
val resourceRootCalc = ClassLoaderResourceRootFIlePathCalculator(keyResourcePath)
|
||||
return getResources(keyResourcePath).asSequence().mapNotNull { url ->
|
||||
if (url.protocol == "jar") {
|
||||
(url.openConnection() as? JarURLConnection)?.jarFileURL?.toFileOrNull()
|
||||
} else {
|
||||
url.toFileOrNull()?.let { resourceRootCalc(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun ClassLoader.classPathFromTypicalResourceUrls(): Sequence<File> =
|
||||
// roots without manifest cases are detected in some test scenarios
|
||||
// manifests without containing directory entries are detected in some optimized jars, e.g. after proguard
|
||||
// TODO: investigate whether getting resources with empty name works in all situations
|
||||
(rawClassPathFromKeyResourcePath("") + rawClassPathFromKeyResourcePath(JAR_MANIFEST_RESOURCE_NAME))
|
||||
.distinct()
|
||||
.filter { it.isValidClasspathFile() }
|
||||
|
||||
private fun File.unpackJarCollection(rootTempDir: File): Sequence<File> {
|
||||
val targetDir = File.createTempFile(nameWithoutExtension, null, rootTempDir).apply {
|
||||
delete()
|
||||
mkdir()
|
||||
}
|
||||
|
||||
return try {
|
||||
ArrayList<File>().apply {
|
||||
JarInputStream(FileInputStream(this@unpackJarCollection)).use { jarInputStream ->
|
||||
for (classesDir in JAR_COLLECTIONS_CLASSES_PATHS) {
|
||||
add(File(targetDir, classesDir))
|
||||
}
|
||||
do {
|
||||
val entry = jarInputStream.nextJarEntry
|
||||
if (entry != null) {
|
||||
try {
|
||||
if (!entry.isDirectory) {
|
||||
val file = File(targetDir, entry.name)
|
||||
if (JAR_COLLECTIONS_LIB_PATHS.any { entry.name.startsWith("$it/") }) {
|
||||
add(file)
|
||||
}
|
||||
file.parentFile.mkdirs()
|
||||
file.outputStream().use { outputStream ->
|
||||
jarInputStream.copyTo(outputStream)
|
||||
outputStream.flush()
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
jarInputStream.closeEntry()
|
||||
}
|
||||
}
|
||||
} while (entry != null)
|
||||
}
|
||||
}.asSequence()
|
||||
} catch (e: Throwable) {
|
||||
targetDir.deleteRecursively()
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
fun classpathFromClasspathProperty(): List<File>? =
|
||||
System.getProperty("java.class.path")
|
||||
?.split(String.format("\\%s", File.pathSeparatorChar).toRegex())
|
||||
?.dropLastWhile(String::isEmpty)
|
||||
?.map(::File)
|
||||
|
||||
fun classpathFromClass(classLoader: ClassLoader, klass: KClass<out Any>): List<File>? =
|
||||
classpathFromFQN(classLoader, klass.qualifiedName!!)
|
||||
|
||||
fun classpathFromClass(klass: KClass<out Any>): List<File>? = classpathFromClass(klass.java.classLoader, klass)
|
||||
|
||||
inline fun <reified T : Any> classpathFromClass(): List<File>? = classpathFromClass(T::class)
|
||||
|
||||
fun classpathFromFQN(classLoader: ClassLoader, fqn: String): List<File>? {
|
||||
val clp = "${fqn.replace('.', '/')}.class"
|
||||
return classLoader
|
||||
.rawClassPathFromKeyResourcePath(clp)
|
||||
.filter { it.isValidClasspathFile() }
|
||||
.toList()
|
||||
.takeIf { it.isNotEmpty() }
|
||||
}
|
||||
|
||||
fun File.matchMaybeVersionedFile(baseName: String) =
|
||||
name == baseName ||
|
||||
// for classes dirs
|
||||
name == baseName.removeSuffix(".jar") ||
|
||||
Regex(Regex.escape(baseName.removeSuffix(".jar")) + "(-\\d.*)?\\.jar").matches(name)
|
||||
|
||||
fun File.hasParentNamed(baseName: String): Boolean =
|
||||
nameWithoutExtension == baseName || parentFile?.hasParentNamed(baseName) ?: false
|
||||
|
||||
private const val KOTLIN_COMPILER_EMBEDDABLE_JAR = "$KOTLIN_COMPILER_NAME-embeddable.jar"
|
||||
|
||||
// Iterating over classloaders tree in a regular, parent-first order
|
||||
private fun allRelatedClassLoaders(
|
||||
clsLoader: ClassLoader,
|
||||
visited: MutableSet<ClassLoader> = HashSet(),
|
||||
): Sequence<ClassLoader> {
|
||||
if (!visited.add(clsLoader)) return emptySequence()
|
||||
|
||||
val singleParent = clsLoader.parent
|
||||
if (singleParent != null) {
|
||||
return sequenceOf(singleParent).flatMap { allRelatedClassLoaders(it, visited) } + clsLoader
|
||||
}
|
||||
|
||||
return try {
|
||||
val arrayOfClassLoaders = getParentClassLoaders(clsLoader)
|
||||
// TODO: PluginClassLoader uses filtering (mustBeLoadedByPlatform), consider using the same logic, if possible
|
||||
// (untill proper compiling from classloader instead of classpath is implemented)
|
||||
arrayOfClassLoaders.asSequence().flatMap { allRelatedClassLoaders(it, visited) } + clsLoader
|
||||
} catch (e: Throwable) {
|
||||
sequenceOf(clsLoader)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getParentClassLoaders(clsLoader: ClassLoader): Array<ClassLoader> =
|
||||
try {
|
||||
getParentsForNewClassLoader(clsLoader)
|
||||
} catch (exception: NoSuchMethodException) {
|
||||
try {
|
||||
getParentsForOldClassLoader(clsLoader)
|
||||
} catch (exception: NoSuchFieldException) {
|
||||
// Possibly idea sources and kotlin compiler had diverged
|
||||
emptyArray()
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(NoSuchFieldException::class)
|
||||
private fun getParentsForOldClassLoader(clsLoader: ClassLoader): Array<ClassLoader> {
|
||||
// Correct way of getting parents in com.intellij.ide.plugins.cl.PluginClassLoader from IDEA 202 and earlier
|
||||
val field = clsLoader.javaClass.getDeclaredField("myParents") // com.intellij.ide.plugins.cl.PluginClassLoader
|
||||
field.isAccessible = true
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return field.get(clsLoader) as Array<ClassLoader>
|
||||
}
|
||||
|
||||
@Throws(NoSuchMethodException::class)
|
||||
private fun getParentsForNewClassLoader(clsLoader: ClassLoader): Array<ClassLoader> {
|
||||
// Correct way of getting parents in com.intellij.ide.plugins.cl.PluginClassLoader from IDEA 203+
|
||||
val method = clsLoader.javaClass.getDeclaredMethod("getAllParents")
|
||||
method.isAccessible = true
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return method.invoke(clsLoader) as Array<ClassLoader>
|
||||
}
|
||||
|
||||
internal fun List<File>.takeIfContainsAll(vararg keyNames: String): List<File>? =
|
||||
takeIf { classpath ->
|
||||
keyNames.all { key -> classpath.any { it.matchMaybeVersionedFile(key) } }
|
||||
}
|
||||
|
||||
internal fun List<File>.filterIfContainsAll(vararg keyNames: String): List<File>? {
|
||||
val foundKeys = mutableSetOf<String>()
|
||||
val res = arrayListOf<File>()
|
||||
for (cpentry in this) {
|
||||
for (prefix in keyNames) {
|
||||
if (cpentry.matchMaybeVersionedFile(prefix) || (cpentry.isDirectory && cpentry.hasParentNamed(prefix))) {
|
||||
foundKeys.add(prefix)
|
||||
res.add(cpentry)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return res.takeIf { foundKeys.containsAll(keyNames.asList()) }
|
||||
}
|
||||
|
||||
internal fun List<File>.takeIfContainsAny(vararg keyNames: String): List<File>? =
|
||||
takeIf { classpath ->
|
||||
keyNames.any { key -> classpath.any { it.matchMaybeVersionedFile(key) } }
|
||||
}
|
||||
|
||||
fun scriptCompilationClasspathFromContextOrNull(
|
||||
vararg keyNames: String,
|
||||
classLoader: ClassLoader = Thread.currentThread().contextClassLoader,
|
||||
wholeClasspath: Boolean = false,
|
||||
unpackJarCollections: Boolean = false,
|
||||
): List<File>? {
|
||||
fun List<File>.takeAndFilter() =
|
||||
when {
|
||||
isEmpty() -> null
|
||||
wholeClasspath -> takeIfContainsAll(*keyNames)
|
||||
else -> filterIfContainsAll(*keyNames)
|
||||
}
|
||||
|
||||
val fromProperty = System.getProperty(KOTLIN_SCRIPT_CLASSPATH_PROPERTY)?.split(File.pathSeparator)?.map(::File)
|
||||
if (fromProperty != null) return fromProperty
|
||||
|
||||
return classpathFromClassloader(classLoader, unpackJarCollections)?.takeAndFilter()
|
||||
?: classpathFromClasspathProperty()?.takeAndFilter()
|
||||
}
|
||||
|
||||
fun scriptCompilationClasspathFromContext(
|
||||
vararg keyNames: String,
|
||||
classLoader: ClassLoader = Thread.currentThread().contextClassLoader,
|
||||
wholeClasspath: Boolean = false,
|
||||
unpackJarCollections: Boolean = false,
|
||||
): List<File> =
|
||||
scriptCompilationClasspathFromContextOrNull(
|
||||
*keyNames,
|
||||
classLoader = classLoader,
|
||||
wholeClasspath = wholeClasspath,
|
||||
unpackJarCollections = unpackJarCollections,
|
||||
) ?: throw ClasspathExtractionException(
|
||||
"Unable to get script compilation classpath from context, please specify explicit classpath via \"$KOTLIN_SCRIPT_CLASSPATH_PROPERTY\" property",
|
||||
)
|
||||
|
||||
object KotlinJars {
|
||||
|
||||
private val explicitCompilerClasspath: List<File>? by lazy {
|
||||
System.getProperty(KOTLIN_COMPILER_CLASSPATH_PROPERTY)?.split(File.pathSeparator)?.map(::File)
|
||||
?: System.getProperty(KOTLIN_COMPILER_JAR_PROPERTY)
|
||||
?.let(::File)
|
||||
?.takeIf(File::exists)
|
||||
?.let { listOf(it) }
|
||||
}
|
||||
|
||||
val compilerClasspath: List<File> by lazy {
|
||||
findCompilerClasspath(withScripting = false)
|
||||
}
|
||||
|
||||
val compilerWithScriptingClasspath: List<File> by lazy {
|
||||
findCompilerClasspath(withScripting = true)
|
||||
}
|
||||
|
||||
private fun findCompilerClasspath(withScripting: Boolean): List<File> {
|
||||
val kotlinCompilerJars = listOf(
|
||||
KOTLIN_COMPILER_JAR,
|
||||
KOTLIN_COMPILER_EMBEDDABLE_JAR,
|
||||
)
|
||||
val kotlinLibsJars = listOf(
|
||||
KOTLIN_JAVA_STDLIB_JAR,
|
||||
KOTLIN_JAVA_REFLECT_JAR,
|
||||
KOTLIN_JAVA_SCRIPT_RUNTIME_JAR,
|
||||
TROVE4J_JAR,
|
||||
)
|
||||
val kotlinScriptingJars = if (withScripting) {
|
||||
listOf(
|
||||
KOTLIN_SCRIPTING_COMPILER_JAR,
|
||||
KOTLIN_SCRIPTING_COMPILER_EMBEDDABLE_JAR,
|
||||
KOTLIN_SCRIPTING_COMPILER_IMPL_JAR,
|
||||
KOTLIN_SCRIPTING_COMPILER_IMPL_EMBEDDABLE_JAR,
|
||||
KOTLIN_SCRIPTING_COMMON_JAR,
|
||||
KOTLIN_SCRIPTING_JVM_JAR,
|
||||
)
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
|
||||
val kotlinBaseJars = kotlinCompilerJars + kotlinLibsJars + kotlinScriptingJars
|
||||
|
||||
val classpath = explicitCompilerClasspath
|
||||
// search classpath from context classloader and `java.class.path` property
|
||||
?: (
|
||||
classpathFromFQN(
|
||||
classLoader = Thread.currentThread().contextClassLoader,
|
||||
fqn = "org.jetbrains.kotlin.cli.jvm.K2JVMCompiler",
|
||||
)
|
||||
?: classpathFromClassloader(Thread.currentThread().contextClassLoader)?.takeIf { it.isNotEmpty() }
|
||||
?: classpathFromClasspathProperty()
|
||||
)?.filter { f -> kotlinBaseJars.any { f.matchMaybeVersionedFile(it) } }
|
||||
?.takeIf { it.isNotEmpty() }
|
||||
// if autodetected, additionally check for presence of the compiler jars
|
||||
if (classpath == null ||
|
||||
(
|
||||
explicitCompilerClasspath == null &&
|
||||
classpath.none { f ->
|
||||
kotlinCompilerJars.any { f.matchMaybeVersionedFile(it) }
|
||||
}
|
||||
)
|
||||
) {
|
||||
throw FileNotFoundException(
|
||||
"Cannot find kotlin compiler jar, set kotlin.compiler.classpath property to proper location",
|
||||
)
|
||||
}
|
||||
return classpath
|
||||
}
|
||||
|
||||
fun getLib(
|
||||
propertyName: String,
|
||||
jarName: String,
|
||||
markerClass: KClass<*>,
|
||||
classLoader: ClassLoader? = null,
|
||||
): File? =
|
||||
getExplicitLib(propertyName, jarName)
|
||||
?: run {
|
||||
val requestedClassloader = classLoader ?: Thread.currentThread().contextClassLoader
|
||||
val byName =
|
||||
if (requestedClassloader == markerClass.java.classLoader) {
|
||||
null
|
||||
} else {
|
||||
tryGetResourcePathForClassByName(markerClass.java.name, requestedClassloader)
|
||||
}
|
||||
byName ?: tryGetResourcePathForClass(markerClass.java)
|
||||
}?.takeIf(File::exists)
|
||||
|
||||
fun getLib(
|
||||
propertyName: String,
|
||||
jarName: String,
|
||||
markerClassName: String,
|
||||
classLoader: ClassLoader? = null,
|
||||
): File? =
|
||||
getExplicitLib(propertyName, jarName)
|
||||
?: tryGetResourcePathForClassByName(
|
||||
markerClassName,
|
||||
classLoader ?: Thread.currentThread().contextClassLoader,
|
||||
)?.takeIf(File::exists)
|
||||
|
||||
private fun getExplicitLib(propertyName: String, jarName: String) =
|
||||
System.getProperty(propertyName)?.let(::File)?.takeIf(File::exists)
|
||||
?: explicitCompilerClasspath?.firstOrNull { it.matchMaybeVersionedFile(jarName) }?.takeIf(File::exists)
|
||||
|
||||
val stdlibOrNull: File? by lazy {
|
||||
System.getProperty(KOTLIN_STDLIB_JAR_PROPERTY)?.let(::File)?.takeIf(File::exists)
|
||||
?: getLib(
|
||||
KOTLIN_RUNTIME_JAR_PROPERTY,
|
||||
KOTLIN_JAVA_STDLIB_JAR,
|
||||
JvmStatic::class,
|
||||
)
|
||||
}
|
||||
|
||||
val stdlib: File by lazy {
|
||||
stdlibOrNull ?: throw Exception(
|
||||
"Unable to find kotlin stdlib, please specify it explicitly via \"$KOTLIN_STDLIB_JAR_PROPERTY\" property",
|
||||
)
|
||||
}
|
||||
|
||||
val reflectOrNull: File? by lazy {
|
||||
getLib(
|
||||
KOTLIN_REFLECT_JAR_PROPERTY,
|
||||
KOTLIN_JAVA_REFLECT_JAR,
|
||||
"kotlin.reflect.full.KClasses", // using a class that is a part of the kotlin-reflect.jar
|
||||
)
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package org.jetbrains.kotlinx.dataframe
|
||||
|
||||
import org.jetbrains.kotlin.test.builders.TestConfigurationBuilder
|
||||
|
||||
fun TestConfigurationBuilder.commonFirWithPluginFrontendConfiguration() {
|
||||
useConfigurators(
|
||||
::ExtensionRegistrarConfigurator,
|
||||
)
|
||||
}
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright 2010-2018 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 org.jetbrains.kotlinx.dataframe
|
||||
|
||||
import java.io.File
|
||||
import java.net.JarURLConnection
|
||||
import java.net.URI
|
||||
import java.net.URL
|
||||
|
||||
// Based on an implementation in com.intellij.openapi.application.PathManager.getResourceRoot
|
||||
|
||||
internal fun getResourceRoot(context: Class<*>, path: String): String? {
|
||||
var url: URL? = context.getResource(path)
|
||||
if (url == null) {
|
||||
url = ClassLoader.getSystemResource(path.substring(1))
|
||||
}
|
||||
return if (url != null) extractRoot(url, path) else null
|
||||
}
|
||||
|
||||
private const val JAR_PROTOCOL = "jar"
|
||||
private const val FILE_PROTOCOL = "file"
|
||||
private const val JAR_SEPARATOR = "!/"
|
||||
private const val SCHEME_SEPARATOR = "://"
|
||||
|
||||
private fun extractRoot(resourceURL: URL, resourcePath: String): String? {
|
||||
if (!resourcePath.startsWith('/') || resourcePath.startsWith('\\')) return null
|
||||
|
||||
var resultPath: String? = null
|
||||
val protocol = resourceURL.protocol
|
||||
if (protocol == FILE_PROTOCOL) {
|
||||
val path = resourceURL.toFileOrNull()!!.path
|
||||
val testPath = path.replace('\\', '/')
|
||||
val testResourcePath = resourcePath.replace('\\', '/')
|
||||
if (testPath.endsWith(testResourcePath, ignoreCase = true)) {
|
||||
resultPath = path.substring(0, path.length - resourcePath.length)
|
||||
}
|
||||
} else if (protocol == JAR_PROTOCOL) {
|
||||
val paths = splitJarUrl(resourceURL.file)
|
||||
if (paths?.first != null) {
|
||||
resultPath = File(paths.first).canonicalPath
|
||||
}
|
||||
}
|
||||
|
||||
return resultPath?.trimEnd(File.separatorChar)
|
||||
}
|
||||
|
||||
private fun splitJarUrl(url: String): Pair<String, String>? {
|
||||
val pivot = url.indexOf(JAR_SEPARATOR).takeIf { it >= 0 } ?: return null
|
||||
|
||||
val resourcePath = url.substring(pivot + 2)
|
||||
var jarPath = url.substring(0, pivot)
|
||||
|
||||
if (jarPath.startsWith(JAR_PROTOCOL + ":")) {
|
||||
jarPath = jarPath.substring(JAR_PROTOCOL.length + 1)
|
||||
}
|
||||
|
||||
if (jarPath.startsWith(FILE_PROTOCOL)) {
|
||||
try {
|
||||
jarPath = URI(jarPath).toURL().toFileOrNull()!!.path.replace('\\', '/')
|
||||
} catch (e: Exception) {
|
||||
jarPath = jarPath.substring(FILE_PROTOCOL.length)
|
||||
if (jarPath.startsWith(SCHEME_SEPARATOR)) {
|
||||
jarPath = jarPath.substring(SCHEME_SEPARATOR.length)
|
||||
} else if (jarPath.startsWith(':')) {
|
||||
jarPath = jarPath.substring(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
return Pair(jarPath, resourcePath)
|
||||
}
|
||||
|
||||
fun tryGetResourcePathForClass(aClass: Class<*>): File? {
|
||||
val path = "/" + aClass.name.replace('.', '/') + ".class"
|
||||
return getResourceRoot(aClass, path)?.let {
|
||||
File(it).absoluteFile
|
||||
}
|
||||
}
|
||||
|
||||
fun getResourcePathForClass(aClass: Class<*>): File =
|
||||
tryGetResourcePathForClass(aClass)
|
||||
?: throw IllegalStateException("Resource for class: ${aClass.name} not found")
|
||||
|
||||
fun tryGetResourcePathForClassByName(name: String, classLoader: ClassLoader): File? =
|
||||
try {
|
||||
classLoader.loadClass(name)?.let(::tryGetResourcePathForClass)
|
||||
} catch (_: ClassNotFoundException) {
|
||||
null
|
||||
} catch (_: NoClassDefFoundError) {
|
||||
null
|
||||
}
|
||||
|
||||
internal fun URL.toFileOrNull() =
|
||||
try {
|
||||
File(toURI())
|
||||
} catch (e: IllegalArgumentException) {
|
||||
null
|
||||
} catch (e: java.net.URISyntaxException) {
|
||||
null
|
||||
} ?: run {
|
||||
if (protocol != "file") {
|
||||
null
|
||||
} else {
|
||||
File(file)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun URL.toContainingJarOrNull(): File? =
|
||||
if (protocol == "jar") {
|
||||
(openConnection() as? JarURLConnection)?.jarFileURL?.toFileOrNull()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
package org.jetbrains.kotlinx.dataframe.services
|
||||
|
||||
import org.jetbrains.kotlin.test.services.TemporaryDirectoryManager
|
||||
import org.jetbrains.kotlin.test.services.TestServices
|
||||
import org.jetbrains.kotlin.test.services.testInfo
|
||||
import org.jetbrains.kotlin.test.util.KtTestUtil
|
||||
import java.io.File
|
||||
import java.nio.file.Paths
|
||||
import java.util.Locale
|
||||
import kotlin.io.path.ExperimentalPathApi
|
||||
import kotlin.io.path.deleteRecursively
|
||||
|
||||
// Copied from org.jetbrains.kotlin.test.services.impl.TemporaryDirectoryManagerImpl
|
||||
// because it uses NioFiles#deleteRecursively and throws method not found as a result.
|
||||
class TemporaryDirectoryManagerImplFixed(testServices: TestServices) : TemporaryDirectoryManager(testServices) {
|
||||
private val cache = mutableMapOf<String, File>()
|
||||
private val rootTempDir = lazy {
|
||||
val testInfo = testServices.testInfo
|
||||
val className = testInfo.className
|
||||
val methodName = testInfo.methodName
|
||||
if (!onWindows && className.length + methodName.length < 255) {
|
||||
return@lazy KtTestUtil.tmpDirForTest(className, methodName)
|
||||
}
|
||||
|
||||
// This code will simplify directory name for windows. This is needed because there can occur errors due to long name
|
||||
val lastDot = className.lastIndexOf('.')
|
||||
val simplifiedClassName = className.substring(lastDot + 1).getOnlyUpperCaseSymbols()
|
||||
val simplifiedMethodName = methodName.getOnlyUpperCaseSymbols()
|
||||
KtTestUtil.tmpDirForTest(simplifiedClassName, simplifiedMethodName)
|
||||
}
|
||||
|
||||
override val rootDir: File
|
||||
get() = rootTempDir.value
|
||||
|
||||
override fun getOrCreateTempDirectory(name: String): File =
|
||||
cache.getOrPut(name) {
|
||||
KtTestUtil.tmpDir(rootDir, name)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalPathApi::class)
|
||||
override fun cleanupTemporaryDirectories() {
|
||||
cache.clear()
|
||||
|
||||
if (rootTempDir.isInitialized()) {
|
||||
Paths.get(rootDir.path).deleteRecursively()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val onWindows: Boolean =
|
||||
System.getProperty("os.name").lowercase(Locale.getDefault()).contains("windows")
|
||||
|
||||
private fun String.getOnlyUpperCaseSymbols(): String =
|
||||
this.filter { it.isUpperCase() || it == '$' }
|
||||
.toList()
|
||||
.joinToString(separator = "")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user