Refactor and fix script annotation processing

This commit is contained in:
Ilya Chernikov
2016-12-08 22:23:38 +01:00
parent 1cc85df78e
commit b1cd98f2d5
12 changed files with 390 additions and 208 deletions
@@ -1,144 +0,0 @@
/*
* 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
import kotlin.reflect.*
import kotlin.reflect.jvm.jvmErasure
fun tryConstructClassFromStringArgs(scriptClass: Class<*>, scriptArgs: List<String>): Any? {
try {
return scriptClass.getConstructor(Array<String>::class.java).newInstance(scriptArgs.toTypedArray())
}
catch (e: NoSuchMethodException) {
for (ctor in scriptClass.kotlin.constructors) {
val mapping = tryCreateCallableMapping(ctor, scriptArgs, StringArgsConverter())
if (mapping != null) {
try {
return ctor.callBy(mapping)
}
catch (e: Exception) { // TODO: find the exact exception type thrown then callBy fails
}
}
}
}
return null
}
fun tryCreateCallableMapping(callable: KCallable<*>, args: List<Any?>): Map<KParameter, Any?>? =
tryCreateCallableMapping(callable, args, AnyArgsConverter())
private interface ArgsConverter<T> {
data class Result(val v: Any?, val argsConsumed: Int)
fun convert(parameter: KParameter, args: List<T>, startArgIndex: Int): Result?
}
private fun <T: Any?> tryCreateCallableMapping(callable: KCallable<*>, args: List<T>, converter: ArgsConverter<T>): Map<KParameter, Any?>? {
var argIdx = 0
val res = mutableMapOf<KParameter, Any?>()
for (par in callable.parameters) {
val (compatibleArg, argsConsumed) = converter.convert(par, args, argIdx) ?: return null
if (argsConsumed > 0) {
res.put(par, compatibleArg)
argIdx += argsConsumed
}
}
return res
}
private class StringArgsConverter : ArgsConverter<String> {
override fun convert(parameter: KParameter, args: List<String>, startArgIndex: Int): ArgsConverter.Result? {
fun convertPrimitive(type: KType?, arg: String): Any? =
when (type?.classifier) {
String::class -> arg
Int::class -> arg.toInt()
Long::class -> arg.toLong()
Short::class -> arg.toShort()
Byte::class -> arg.toByte()
Char::class -> arg[0]
Float::class -> arg.toFloat()
Double::class -> arg.toDouble()
Boolean::class -> arg.toBoolean()
else -> null
}
fun convertArray(type: KType?, args: List<String>): Any? =
when (type?.classifier) {
String::class -> args.toTypedArray()
Int::class -> args.map(String::toInt).toTypedArray()
Long::class -> args.map(String::toLong).toTypedArray()
Short::class -> args.map(String::toShort).toTypedArray()
Byte::class -> args.map(String::toByte).toTypedArray()
Char::class -> args.map { it[0] }.toTypedArray()
Float::class -> args.map(String::toFloat).toTypedArray()
Double::class -> args.map(String::toDouble).toTypedArray()
Boolean::class -> args.map(String::toBoolean).toTypedArray()
else -> null
}
try {
if (startArgIndex >= args.size && parameter.isOptional)
return ArgsConverter.Result(null, 0)
if (startArgIndex < args.size) {
val primArgCandidate = convertPrimitive(parameter.type, args[startArgIndex])
if (primArgCandidate != null)
return ArgsConverter.Result(primArgCandidate, 1)
}
if ((parameter.type.classifier as? KClass<*>)?.qualifiedName == Array<Any>::class.qualifiedName) {
val arrCompType = parameter.type.arguments.getOrNull(0)?.type
val arrayArgCandidate = convertArray(arrCompType, args.drop(startArgIndex))
if (arrayArgCandidate != null)
return ArgsConverter.Result(arrayArgCandidate, args.size - startArgIndex)
}
}
catch (e: NumberFormatException) {}
return null
}
}
private class AnyArgsConverter : ArgsConverter<Any?> {
override fun convert(parameter: KParameter, args: List<Any?>, startArgIndex: Int): ArgsConverter.Result? {
fun convertSingle(type: KType, arg: Any?): Any? = when {
type.classifier == arg?.javaClass?.kotlin -> arg
type.jvmErasure.java.isAssignableFrom(arg?.javaClass) -> arg
else -> null
}
if (startArgIndex >= args.size && parameter.isOptional)
return ArgsConverter.Result(null, 0)
if (startArgIndex < args.size) {
val primArgCandidate = convertSingle(parameter.type, args[startArgIndex])
if (primArgCandidate != null)
return ArgsConverter.Result(primArgCandidate, 1)
}
// TODO: add vararg support
return null
}
}
@@ -16,7 +16,7 @@
package org.jetbrains.kotlin.cli.common.repl
import org.jetbrains.kotlin.cli.common.tryCreateCallableMapping
import org.jetbrains.kotlin.utils.tryCreateCallableMapping
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
import java.io.File
import java.net.URLClassLoader
@@ -34,7 +34,7 @@ import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.common.ExitCode
import org.jetbrains.kotlin.cli.common.messages.*
import org.jetbrains.kotlin.cli.common.output.outputUtils.writeAll
import org.jetbrains.kotlin.cli.common.tryConstructClassFromStringArgs
import org.jetbrains.kotlin.utils.tryConstructClassFromStringArgs
import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
import org.jetbrains.kotlin.cli.jvm.config.*
import org.jetbrains.kotlin.codegen.ClassBuilderFactories
@@ -101,9 +101,8 @@ open class KotlinScriptDefinitionFromAnnotatedTemplate(
// TODO: consider advanced matching using semantic similar to actual resolving
acceptedAnnotations.find { ann ->
psiAnn.typeName.let { it == ann.simpleName || it == ann.qualifiedName }
}?.let { KtAnnotationWrapper(psiAnn, classLoader.loadClass(it.qualifiedName).kotlin as KClass<out Annotation>) }
}?.let { constructAnnotation(psiAnn, classLoader.loadClass(it.qualifiedName).kotlin as KClass<out Annotation>) }
}
.map { it.getProxy(classLoader) }
}
catch (ex: Throwable) {
logClassloadingError(ex)
@@ -23,10 +23,9 @@ import org.jetbrains.kotlin.psi.KtUserType
import org.jetbrains.kotlin.resolve.BindingTraceContext
import org.jetbrains.kotlin.resolve.constants.evaluate.ConstantExpressionEvaluator
import org.jetbrains.kotlin.types.TypeUtils
import java.lang.reflect.InvocationHandler
import java.lang.reflect.Method
import java.lang.reflect.Proxy
import org.jetbrains.kotlin.utils.tryCreateCallableMappingFromNamedArgs
import kotlin.reflect.KClass
import kotlin.reflect.KParameter
import kotlin.reflect.primaryConstructor
internal val KtAnnotationEntry.typeName: String get() = (typeReference?.typeElement as? KtUserType)?.referencedName.orAnonymous()
@@ -34,50 +33,26 @@ internal val KtAnnotationEntry.typeName: String get() = (typeReference?.typeElem
internal fun String?.orAnonymous(kind: String = ""): String =
this ?: "<anonymous" + (if (kind.isNotBlank()) " $kind" else "") + ">"
internal class KtAnnotationWrapper(val psi: KtAnnotationEntry, val targetClass: KClass<out Annotation>) {
val name: String get() = psi.typeName
internal fun constructAnnotation(psi: KtAnnotationEntry, targetClass: KClass<out Annotation>): Annotation {
val valueArguments: Map<String, Any?> by lazy {
var namedStarted = false
val res = hashMapOf<String, Any?>()
// TODO: annotation constructors unsupported yet in kotlin reflection, test and correct when they will be ready
val targetAnnParams = targetClass.primaryConstructor?.parameters
psi.valueArguments.mapIndexed { i, arg ->
val evaluator = ConstantExpressionEvaluator(DefaultBuiltIns.Instance, LanguageVersionSettingsImpl.DEFAULT)
val trace = BindingTraceContext()
val result = evaluator.evaluateToConstantValue(arg.getArgumentExpression()!!, trace, TypeUtils.NO_EXPECTED_TYPE)
// TODO: consider inspecting `trace` to find diagnostics reported during the computation (such as division by zero, integer overflow, invalid annotation parameters etc.)
val argName = arg.getArgumentName()?.asName?.toString()
// TODO: consider reusing arguments mapping logic from compiler code
// TODO: find out how to properly report problems from here (now using bogus arg names as an indicator)
val paramName = when {
argName == null && !namedStarted && targetAnnParams == null -> "$" // TODO: using invalid name here. Drop when annotation constructors will be accessible (se above)
argName == null && !namedStarted -> targetAnnParams?.get(i)?.name ?: "$(Unnamed argument for $name at $i)"
argName == null && namedStarted -> "$(Invalid argument sequence for $name at arg $i)"
targetAnnParams != null && targetAnnParams.none { it.name == argName } -> "$(Unknown argument $argName for $name)"
else -> {
namedStarted = true
argName!!
}
}
res.put(paramName, result?.value)
}
res
val valueArguments = psi.valueArguments.map { arg ->
val evaluator = ConstantExpressionEvaluator(DefaultBuiltIns.Instance, LanguageVersionSettingsImpl.DEFAULT)
val trace = BindingTraceContext()
val result = evaluator.evaluateToConstantValue(arg.getArgumentExpression()!!, trace, TypeUtils.NO_EXPECTED_TYPE)
// TODO: consider inspecting `trace` to find diagnostics reported during the computation (such as division by zero, integer overflow, invalid annotation parameters etc.)
val argName = arg.getArgumentName()?.asName?.toString()
argName to result?.value
}
val mappedArguments: Map<KParameter, Any?> =
tryCreateCallableMappingFromNamedArgs(targetClass.constructors.first(), valueArguments)
?: return InvalidScriptResolverAnnotation(psi.typeName, valueArguments)
internal class AnnProxyInvocationHandler(val targetAnnClass: KClass<out Annotation>, val annParams: Map<String, Any?>) : InvocationHandler {
override fun invoke(proxy: Any?, method: Method?, params: Array<out Any>?): Any? = method?.let {
method?.name?.let { annParams[it] }
}
try {
return targetClass.primaryConstructor!!.callBy(mappedArguments)
}
catch (ex: Exception) {
return InvalidScriptResolverAnnotation(psi.typeName, valueArguments, ex)
}
fun getProxy(classLoader: ClassLoader): Annotation =
try {
Proxy.newProxyInstance(classLoader, arrayOf(targetClass.java), AnnProxyInvocationHandler(targetClass, valueArguments)) as Annotation
}
catch (ex: Exception) {
InvalidScriptResolverAnnotation(name, valueArguments, ex)
}
}
class InvalidScriptResolverAnnotation(val name: String, val annParams: Map<String, Any?>, val error: Exception? = null) : Annotation
class InvalidScriptResolverAnnotation(val name: String, val annParams: List<Pair<String?, Any?>>?, val error: Exception? = null) : Annotation
@@ -19,7 +19,7 @@ package org.jetbrains.kotlin.scripts
import com.intellij.openapi.util.Disposer
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.common.messages.*
import org.jetbrains.kotlin.cli.common.tryConstructClassFromStringArgs
import org.jetbrains.kotlin.utils.tryConstructClassFromStringArgs
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler
@@ -35,6 +35,8 @@ import org.jetbrains.kotlin.utils.PathUtil
import org.junit.Assert
import org.junit.Test
import java.io.File
import java.io.OutputStream
import java.io.PrintStream
import java.lang.Exception
import java.lang.reflect.InvocationTargetException
import java.net.URL
@@ -91,7 +93,14 @@ class ScriptTemplateTest {
@Test
fun testScriptWithDependsAnn2() {
Assert.assertNull(compileScript("fib_ext_ann2.kts", ScriptWithIntParamAndDummyResolver::class, null, includeKotlinRuntime = false))
val savedErr = System.err
try {
System.setErr(PrintStream(NullOutputStream()))
Assert.assertNull(compileScript("fib_ext_ann2.kts", ScriptWithIntParamAndDummyResolver::class, null, includeKotlinRuntime = false))
}
finally {
System.setErr(savedErr)
}
val aClass = compileScript("fib_ext_ann2.kts", ScriptWithIntParam::class, null, includeKotlinRuntime = false)
Assert.assertNotNull(aClass)
@@ -324,13 +333,13 @@ class TestKotlinScriptDependenciesResolver : TestKotlinScriptDummyDependenciesRe
val cp = script.annotations.flatMap {
when (it) {
is DependsOn -> if (it.path == "@{runtime}") listOf(kotlinPaths.runtimePath, kotlinPaths.scriptRuntimePath) else listOf(File(it.path))
is DependsOnTwo -> listOf(it.path1, it.path2).flatMap {
is DependsOnTwo -> listOf(it.path1, it.path2).flatMap { it?.let {
when {
it.isBlank() -> emptyList()
it == "@{runtime}" -> listOf(kotlinPaths.runtimePath, kotlinPaths.scriptRuntimePath)
else -> listOf(File(it))
}
}
}}
is InvalidScriptResolverAnnotation -> throw Exception("Invalid annotation ${it.name}", it.error)
else -> throw Exception("Unknown annotation ${it.javaClass}")
}
@@ -394,3 +403,9 @@ annotation class DependsOn(val path: String)
@Target(AnnotationTarget.FILE)
@Retention(AnnotationRetention.RUNTIME)
annotation class DependsOnTwo(val unused: String = "", val path1: String = "", val path2: String = "")
private class NullOutputStream : OutputStream() {
override fun write(b: Int) { }
override fun write(b: ByteArray) { }
override fun write(b: ByteArray, off: Int, len: Int) { }
}
@@ -19,7 +19,7 @@ package org.jetbrains.kotlin.scripts
import com.intellij.openapi.util.Disposer
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.common.messages.*
import org.jetbrains.kotlin.cli.common.tryConstructClassFromStringArgs
import org.jetbrains.kotlin.utils.tryConstructClassFromStringArgs
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler
@@ -0,0 +1,86 @@
/*
* 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.util
import org.jetbrains.kotlin.utils.tryCreateCallableMappingFromNamedArgs
import org.jetbrains.kotlin.utils.tryCreateCallableMappingFromStringArgs
import org.junit.Assert
import org.junit.Test
import kotlin.reflect.KParameter
class ArgsToParamsMatchingTest {
@Test
fun testMatchFromStrings() {
Assert.assertNull(tryCreateCallableMappingFromStringArgs(::foo, listOf()))
Assert.assertNull(tryCreateCallableMappingFromStringArgs(::foo, listOf("1", "2")))
assertParamMapsEquals(tryCreateCallableMappingFromStringArgs(::foo, listOf("1", "2", "s", "0.1")),
"i" to 1, "b" to 2.toByte(), "c" to 's', "d" to 0.1)
Assert.assertNull(tryCreateCallableMappingFromStringArgs(::foo, listOf("1", "258", "s", "0.1")))
Assert.assertNull(tryCreateCallableMappingFromStringArgs(::foo, listOf("1", "258", "s", "0")))
Assert.assertNull(tryCreateCallableMappingFromStringArgs(::foo, listOf("1", "258", "sss", "0.1")))
assertParamMapsEquals(tryCreateCallableMappingFromStringArgs(::foo, listOf("1", "2", "s", "0.1", "abc", "true", "1", "2", "3")),
"i" to 1, "b" to 2.toByte(), "c" to 's', "d" to 0.1, "s" to "abc", "t" to true, "v" to arrayOf(1L, 2L, 3L))
}
@Test
fun testMatchNamed() {
Assert.assertNull(tryCreateCallableMappingFromNamedArgs(::foo, listOf()))
Assert.assertNull(tryCreateCallableMappingFromNamedArgs(::foo, listOf(null to 1, null to 2)))
assertParamMapsEquals(tryCreateCallableMappingFromNamedArgs(::foo, listOf(null to 1, null to 2.toByte(), null to 's', null to 0.1)),
"i" to 1, "b" to 2.toByte(), "c" to 's', "d" to 0.1)
assertParamMapsEquals(tryCreateCallableMappingFromNamedArgs(::foo, listOf(null to 1, null to 2.toByte(), "c" to 's', "d" to 0.1)),
"i" to 1, "b" to 2.toByte(), "c" to 's', "d" to 0.1)
assertParamMapsEquals(tryCreateCallableMappingFromNamedArgs(::foo, listOf(null to 1, null to 2.toByte(), "d" to 0.1, "c" to 's')),
"i" to 1, "b" to 2.toByte(), "c" to 's', "d" to 0.1)
assertParamMapsEquals(tryCreateCallableMappingFromNamedArgs(::foo, listOf(null to 1, null to 2.toByte(), null to 's', null to 0.1, "v" to arrayOf(1L, 2L, 3L))),
"i" to 1, "b" to 2.toByte(), "c" to 's', "d" to 0.1, "v" to arrayOf(1L, 2L, 3L))
Assert.assertNull(tryCreateCallableMappingFromNamedArgs(::foo, listOf(null to 1, null to 2.toByte(), null to 's', "x" to 0.1))) // wrong name
Assert.assertNull(tryCreateCallableMappingFromNamedArgs(::foo, listOf(null to 1, null to 2.toByte(), "c" to 's', null to 0.1))) // unnamed after named
}
}
private fun assertParamMapsEquals(actuals: Map<KParameter, Any?>?, vararg expected: Pair<String, Any?>) {
Assert.assertNotNull(actuals)
val stringifiedActuals = actuals!!.mapKeys { it.key.name }
val mappedExpected = expected.toMap()
if (expected != stringifiedActuals) {
Assert.assertEquals(stringifiedActuals.keys, mappedExpected.keys)
mappedExpected.forEach { exp ->
val actVal = stringifiedActuals[exp.key]
if (exp.value != actVal) {
val msg = "Unexpected value for key '${exp.key}'; expected: ${exp.value}, actual: $actVal"
if ((exp.value?.javaClass?.isArray ?: false) && (actVal?.javaClass?.isArray ?: false )) {
Assert.assertArrayEquals(msg, arrayOf(exp.value), arrayOf(actVal)) // tricking Array.deepEquals to compare single element arrays (instead of tedious casting to typed array)
}
else {
Assert.assertEquals(msg, exp.value, actVal)
}
}
}
}
}
private fun foo(i: Int, b: Byte, c: Char, d: Double = 0.0, s: String = "", t: Boolean = true, vararg v: Long) {}
@@ -0,0 +1,254 @@
/*
* 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.utils
import org.jetbrains.kotlin.utils.addToStdlib.check
import kotlin.reflect.KCallable
import kotlin.reflect.KClass
import kotlin.reflect.KParameter
import kotlin.reflect.KType
import kotlin.reflect.jvm.jvmErasure
fun tryConstructClassFromStringArgs(clazz: Class<*>, args: List<String>): Any? {
try {
return clazz.getConstructor(Array<String>::class.java).newInstance(args.toTypedArray())
}
catch (e: NoSuchMethodException) {
for (ctor in clazz.kotlin.constructors) {
val mapping = tryCreateCallableMappingFromStringArgs(ctor, args)
if (mapping != null) {
try {
return ctor.callBy(mapping)
}
catch (e: Exception) { // TODO: find the exact exception type thrown then callBy fails
}
}
}
}
return null
}
fun tryCreateCallableMapping(callable: KCallable<*>, args: List<Any?>): Map<KParameter, Any?>? =
tryCreateCallableMapping(callable, args.map { NamedArgument(null, it) }.iterator(), AnyArgsConverter())
fun tryCreateCallableMappingFromStringArgs(callable: KCallable<*>, args: List<String>): Map<KParameter, Any?>? =
tryCreateCallableMapping(callable, args.map { NamedArgument(null, it) }.iterator(), StringArgsConverter())
fun tryCreateCallableMappingFromNamedArgs(callable: KCallable<*>, args: List<Pair<String?, Any?>>): Map<KParameter, Any?>? =
tryCreateCallableMapping(callable, args.map { NamedArgument(it.first, it.second) }.iterator(), AnyArgsConverter())
// ------------------------------------------------
private data class NamedArgument<out T>(val name: String?, val value: T?)
private interface ArgsConverter<T> {
sealed class Result {
object Failure : Result()
class Success(val v: Any?) : Result()
}
fun tryConvertSingle(parameter: KParameter, arg: NamedArgument<T>): Result
fun tryConvertVararg(parameter: KParameter, firstArg: NamedArgument<T>, restArgsIt: Iterator<NamedArgument<T>>): Result
fun tryConvertTail(parameter: KParameter, firstArg: NamedArgument<T>, restArgsIt: Iterator<NamedArgument<T>>): Result
}
private enum class ArgsTraversalState { UNNAMED, NAMED, TAIL }
private fun <T> tryCreateCallableMapping(callable: KCallable<*>, args: Iterator<NamedArgument<T>>, converter: ArgsConverter<T>): Map<KParameter, Any?>? {
val res = mutableMapOf<KParameter, Any?>()
var state = ArgsTraversalState.UNNAMED
val unboundParams = callable.parameters.toMutableList()
val argIt = args.iterator()
while (argIt.hasNext()) {
if (unboundParams.isEmpty()) return null // failed to match: no param left for the arg
val arg = argIt.next()
when (state) {
ArgsTraversalState.UNNAMED -> if (arg.name != null) state = ArgsTraversalState.NAMED
ArgsTraversalState.NAMED -> if (arg.name == null) state = ArgsTraversalState.TAIL
ArgsTraversalState.TAIL -> if (arg.name != null) throw IllegalArgumentException("Illegal mix of named and unnamed arguments")
}
// TODO: check the logic of named/unnamed/tail(vararg or lambda) arguments matching
when (state) {
ArgsTraversalState.UNNAMED -> {
val par = unboundParams.removeAt(0)
// try single argument first
val cvtRes = converter.tryConvertSingle(par, arg)
if (cvtRes is ArgsConverter.Result.Success) {
if (cvtRes.v == null && !par.type.isMarkedNullable) { // TODO: this is not precise check - see comments to the property; consider better approach
// or if we do not allow to overload on nullability, drop this check
return null // failed to match: null for a non-nullable value
}
res.put(par, cvtRes.v)
}
// TODO: check if second part of the condition is necessary
else if ((par.type.classifier as? KClass<*>)?.let { it.java.isArray || it.qualifiedName == Array<Any>::class.qualifiedName } ?: false) {
// try vararg
val cvtVRes = converter.tryConvertVararg(par, arg, argIt)
if (cvtVRes is ArgsConverter.Result.Success) {
res.put(par, cvtVRes.v)
}
else return null // failed to match: no suitable param for unnamed arg
}
else return null // failed to match: no suitable param for unnamed arg
}
ArgsTraversalState.NAMED -> {
assert(arg.name != null)
val parIdx = unboundParams.indexOfFirst { it.name != null && it.name == arg.name }.check { it >= 0 }
?: return null // failed to match: no matching named parameter found
val par = unboundParams.removeAt(parIdx)
val cvtRes = converter.tryConvertSingle(par, arg)
if (cvtRes is ArgsConverter.Result.Success) {
res.put(par, cvtRes.v)
}
else return null // failed to match: cannot convert arg to param's type
}
ArgsTraversalState.TAIL -> {
assert(arg.name == null)
val par = unboundParams.removeAt(unboundParams.lastIndex)
val cvtVRes = converter.tryConvertTail(par, arg, argIt)
if (cvtVRes is ArgsConverter.Result.Success) {
if (argIt.hasNext()) return null // failed to match: not all tail args are consumed
res.put(par, cvtVRes.v)
}
else return null // failed to match: no suitable param for tail arg(s)
}
}
}
return when {
unboundParams.any { !it.isOptional && !it.isVararg } -> null // fail to match: non-optional params remained
else -> res
}
}
private class StringArgsConverter : ArgsConverter<String> {
override fun tryConvertSingle(parameter: KParameter, arg: NamedArgument<String>): ArgsConverter.Result {
fun convertPrimitive(type: KType?, arg: String): Any? =
when (type?.classifier) {
String::class -> arg
Int::class -> arg.toInt()
Long::class -> arg.toLong()
Short::class -> arg.toShort()
Byte::class -> arg.toByte()
Char::class -> if (arg.length != 1) null else arg[0]
Float::class -> arg.toFloat()
Double::class -> arg.toDouble()
Boolean::class -> arg.toBoolean()
else -> null
}
try {
if (arg.value != null) {
val primArgCandidate = convertPrimitive(parameter.type, arg.value)
if (primArgCandidate != null)
return ArgsConverter.Result.Success(primArgCandidate)
}
else return ArgsConverter.Result.Success(null)
}
catch (e: NumberFormatException) {}
return ArgsConverter.Result.Failure
}
override fun tryConvertVararg(parameter: KParameter, firstArg: NamedArgument<String>, restArgsIt: Iterator<NamedArgument<String>>): ArgsConverter.Result {
fun convertAnyArray(type: KType?, args: Sequence<String?>): Any? =
when (type?.classifier) {
String::class -> args.toList().toTypedArray()
else -> {
(type?.classifier as? KClass<*>)?.constructors?.forEach { ctor ->
try {
return@convertAnyArray args.map { ctor.call(it) }.toList().toTypedArray()
}
catch (e: Exception) {}
}
null
}
}
fun convertPrimitivesArray(type: KType?, args: Sequence<String?>): Any? =
when (type?.classifier) {
IntArray::class -> args.map { it?.toInt() }.toList().toTypedArray()
LongArray::class -> args.map { it?.toLong() }.toList().toTypedArray()
ShortArray::class -> args.map { it?.toShort() }.toList().toTypedArray()
ByteArray::class -> args.map { it?.toByte() }.toList().toTypedArray()
CharArray::class -> args.map { it?.get(0) }.toList().toTypedArray()
FloatArray::class -> args.map { it?.toFloat() }.toList().toTypedArray()
DoubleArray::class -> args.map { it?.toDouble() }.toList().toTypedArray()
BooleanArray::class -> args.map { it?.toBoolean() }.toList().toTypedArray()
else -> null
}
try {
// TODO: check if second part of the condition is necessary
if ((parameter.type.classifier as? KClass<*>)?.let { it.java.isArray || it.qualifiedName == Array<Any>::class.qualifiedName } ?: false) {
val argsSequence = sequenceOf(firstArg.value) + restArgsIt.asSequence().map { it.value }
val primArrayArgCandidate = convertPrimitivesArray(parameter.type, argsSequence)
if (primArrayArgCandidate != null)
return ArgsConverter.Result.Success(primArrayArgCandidate)
val arrCompType = parameter.type.arguments.getOrNull(0)?.type
val arrayArgCandidate = convertAnyArray(arrCompType, argsSequence)
if (arrayArgCandidate != null)
return ArgsConverter.Result.Success(arrayArgCandidate)
}
}
catch (e: NumberFormatException) {}
return ArgsConverter.Result.Failure
}
override fun tryConvertTail(parameter: KParameter, firstArg: NamedArgument<String>, restArgsIt: Iterator<NamedArgument<String>>): ArgsConverter.Result =
tryConvertVararg(parameter, firstArg, restArgsIt)
}
private class AnyArgsConverter : ArgsConverter<Any> {
override fun tryConvertSingle(parameter: KParameter, arg: NamedArgument<Any>): ArgsConverter.Result {
fun convertPrimitivesArray(type: KType?, arg: Any?): Any? =
when (type?.classifier) {
IntArray::class -> (arg as? Array<Int>)?.toIntArray()
LongArray::class -> (arg as? Array<Long>)?.toLongArray()
ShortArray::class -> (arg as? Array<Short>)?.toShortArray()
ByteArray::class -> (arg as? Array<Byte>)?.toByteArray()
CharArray::class -> (arg as? Array<Char>)?.toCharArray()
FloatArray::class -> (arg as? Array<Float>)?.toFloatArray()
DoubleArray::class -> (arg as? Array<Double>)?.toDoubleArray()
BooleanArray::class -> (arg as? Array<Boolean>)?.toBooleanArray()
else -> null
}
fun convertSingle(type: KType, arg: Any?): Any? = when {
type.classifier == arg?.javaClass?.kotlin -> arg
type.jvmErasure.java.isAssignableFrom(arg?.javaClass) -> arg
else -> null
}
return (convertSingle(parameter.type, arg.value) ?: convertPrimitivesArray(parameter.type, arg.value))
?.let { ArgsConverter.Result.Success(it) }
?: ArgsConverter.Result.Failure
}
override fun tryConvertVararg(parameter: KParameter, firstArg: NamedArgument<Any>, restArgsIt: Iterator<NamedArgument<Any>>): ArgsConverter.Result =
ArgsConverter.Result.Failure
override fun tryConvertTail(parameter: KParameter, firstArg: NamedArgument<Any>, restArgsIt: Iterator<NamedArgument<Any>>): ArgsConverter.Result =
tryConvertSingle(parameter, firstArg)
}
@@ -35,7 +35,7 @@ import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.component.repository.ComponentDependency;
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys;
import org.jetbrains.kotlin.cli.common.ReflectionUtilKt;
import org.jetbrains.kotlin.utils.ReflectionUtilKt;
import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler;
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles;
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment;
@@ -28,7 +28,7 @@ interface Resolver {
class DirectResolver : Resolver {
override fun tryResolve(dependsOn: DependsOn): Iterable<File>? =
dependsOn.value?.let(::File)?.check { it.exists() && (it.isFile || it.isDirectory) }?.let { listOf(it) }
dependsOn.value.check(String::isNotBlank)?.let(::File)?.check { it.exists() && (it.isFile || it.isDirectory) }?.let { listOf(it) }
}
class FlatLibDirectoryResolver(val path: File) : Resolver {
@@ -39,10 +39,10 @@ class FlatLibDirectoryResolver(val path: File) : Resolver {
override fun tryResolve(dependsOn: DependsOn): Iterable<File>? =
// TODO: add coordinates and wildcard matching
dependsOn.value?.let{ File(path, it) }?.check { it.exists() && (it.isFile || it.isDirectory) }?.let { listOf(it) }
dependsOn.value.check(String::isNotBlank)?.let{ File(path, it) }?.check { it.exists() && (it.isFile || it.isDirectory) }?.let { listOf(it) }
companion object {
fun tryCreate(annotation: Repository): FlatLibDirectoryResolver? =
annotation.value?.check(String::isNotBlank)?.let(::File)?.check { it.exists() && it.isDirectory }?.let(::FlatLibDirectoryResolver)
annotation.value.check(String::isNotBlank)?.let(::File)?.check { it.exists() && it.isDirectory }?.let(::FlatLibDirectoryResolver)
}
}
@@ -43,7 +43,7 @@ class MavenResolver(val reportError: ((String) -> Unit)? = null): Resolver {
private fun currentRepos() = if (repos.isEmpty()) arrayListOf(mavenCentral) else repos
private fun String?.isValidParam() = if (this != null && isNotBlank()) true else false
private fun String.isValidParam() = isNotBlank()
override fun tryResolve(dependsOn: DependsOn): Iterable<File>? {
@@ -55,13 +55,10 @@ class MavenResolver(val reportError: ((String) -> Unit)? = null): Resolver {
val artifactId: DefaultArtifact = when {
dependsOn.groupId.isValidParam() || dependsOn.artifactId.isValidParam() -> {
val s1 = dependsOn.groupId
val s2 = dependsOn.artifactId
DefaultArtifact(dependsOn.groupId.orNullIfBlank(), dependsOn.artifactId.orNullIfBlank(), null, dependsOn.version.orNullIfBlank())
}
dependsOn.value.isValidParam() && dependsOn.value.count { it == ':' } == 2 -> {
val s = dependsOn.value
DefaultArtifact(s)
DefaultArtifact(dependsOn.value)
}
else -> {
error("Unknown set of arguments to maven resolver: ${dependsOn.value}")
@@ -84,7 +81,7 @@ class MavenResolver(val reportError: ((String) -> Unit)? = null): Resolver {
}
fun tryAddRepo(annotation: Repository): Boolean {
val urlStr = if (annotation.url.isValidParam()) annotation.url else annotation.value ?: return false
val urlStr = annotation.url.check { it.isValidParam() } ?: annotation.value.check { it.isValidParam() } ?: return false
try {
URL(urlStr)
} catch (_: MalformedURLException) {
@@ -92,7 +89,7 @@ class MavenResolver(val reportError: ((String) -> Unit)? = null): Resolver {
}
repos.add(
RemoteRepository(
if (annotation.id.isValidParam()) annotation.id else "cantral",
if (annotation.id.isValidParam()) annotation.id else "central",
"default",
urlStr
))