fixes after review

This commit is contained in:
Ilya Chernikov
2016-06-24 14:33:48 -07:00
committed by Pavel V. Talanov
parent 5a77d2e92b
commit b0bff64cff
9 changed files with 76 additions and 89 deletions
@@ -69,13 +69,6 @@ class KotlinScriptExternalDependenciesUnion(val dependencies: Iterable<KotlinScr
override val sources: Iterable<File> get() = dependencies.flatMap { it.sources }
}
fun Iterable<File>.isSameClasspathAs(other: Iterable<File>): Boolean {
val c1 = map { it.canonicalPath }.toHashSet()
val c2 = other.map { it.canonicalPath }.toHashSet()
return c1.size == c2.size &&
c1.zip(c2).all { it.first == it.second }
}
data class ScriptParameter(val name: Name, val type: KotlinType)
fun <TF> getFileName(file: TF): String = when (file) {
@@ -24,6 +24,7 @@ import kotlin.concurrent.read
import kotlin.concurrent.write
class KotlinScriptExternalImportsProvider(val project: Project, private val scriptDefinitionProvider: KotlinScriptDefinitionProvider) {
private val cacheLock = ReentrantReadWriteLock()
private val cache = hashMapOf<String, KotlinScriptExternalDependencies>()
private val cacheOfNulls = hashSetOf<String>()
@@ -82,7 +83,7 @@ class KotlinScriptExternalImportsProvider(val project: Project, private val scri
val deps = scriptDef.getDependenciesFor(file, project, oldDeps)
when {
deps != null && (oldDeps == null ||
!deps.classpath.isSameClasspathAs(oldDeps.classpath) || !deps.sources.isSameClasspathAs(oldDeps.sources)) -> {
!deps.classpath.isSamePathListAs(oldDeps.classpath) || !deps.sources.isSamePathListAs(oldDeps.sources)) -> {
// changed or new
cache.put(path, deps)
cacheOfNulls.remove(path)
@@ -104,9 +105,8 @@ class KotlinScriptExternalImportsProvider(val project: Project, private val scri
fun invalidateCaches() {
cacheLock.write {
cache.keys.toList().apply {
cache.clear()
}
cache.clear()
cacheOfNulls.clear()
}
}
@@ -136,4 +136,11 @@ class KotlinScriptExternalImportsProvider(val project: Project, private val scri
fun getInstance(project: Project): KotlinScriptExternalImportsProvider? =
ServiceManager.getService(project, KotlinScriptExternalImportsProvider::class.java)
}
}
}
internal fun Iterable<File>.isSamePathListAs(other: Iterable<File>): Boolean {
val c1 = asSequence().map { it.canonicalPath }
val c2 = other.asSequence().map { it.canonicalPath }
return c1 == c2
}
@@ -16,54 +16,27 @@
package org.jetbrains.kotlin.script
import com.intellij.openapi.util.text.StringUtil
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.builtins.DefaultBuiltIns
import org.jetbrains.kotlin.psi.KtAnnotationEntry
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
internal class KtAnnotationWrapper(val psi: KtAnnotationEntry) {
val name: String
get() = (psi.typeReference?.typeElement as? KtUserType)?.referencedName.orAnonymous()
val valueArguments by lazy {
val valueArguments: List<Pair<String?, Any?>> by lazy {
psi.valueArguments.map {
Pair(it.getArgumentName()?.toString(), convert(it.getArgumentExpression()!!))
val evaluator = ConstantExpressionEvaluator(DefaultBuiltIns.Instance)
val trace = BindingTraceContext()
val result = evaluator.evaluateToConstantValue(it.getArgumentExpression()!!, trace, TypeUtils.NO_EXPECTED_TYPE)
it.getArgumentName()?.asName.toString() to result?.value
// TODO: consider inspecting `trace` to find diagnostics reported during the computation (such as division by zero, integer overflow, invalid annotation parameters etc.)
}
}
internal fun String?.orAnonymous(kind: String = ""): String {
return this ?: "<anonymous" + (if (kind.isNotBlank()) " $kind" else "") + ">"
}
internal fun convert(expression: KtExpression): Any? = when (expression) {
is KtStringTemplateExpression -> {
if (expression.entries.isEmpty())
""
else if (expression.entries.size == 1)
convert(expression.entries[0])
else
""
// TODO: parse expressions, etc. e.g.:
// convertStringTemplateExpression(expression, parent, expression.entries.size - 1)
}
else -> null
}
internal fun convert(entry: KtStringTemplateEntry): Any? = when (entry) {
is KtStringTemplateEntryWithExpression -> convertOrEmpty(entry.expression)
is KtEscapeStringTemplateEntry -> entry.unescapedValue
else -> {
StringUtil.unescapeStringCharacters(entry.text)
}
}
internal fun convertOrEmpty(expression: KtExpression?): Any? {
return if (expression != null) convert(expression) else null
}
internal fun String?.orAnonymous(kind: String = ""): String =
this ?: "<anonymous" + (if (kind.isNotBlank()) " $kind" else "") + ">"
}
fun parseAnnotation(ann: KtAnnotationEntry): Pair<String, Iterable<Any?>> {
val wann = KtAnnotationWrapper(ann)
val vals = wann.valueArguments
return Pair(wann.name, vals)
}
@@ -28,14 +28,12 @@ import org.jetbrains.kotlin.psi.KtAnnotationEntry
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtScript
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import java.io.File
import java.lang.reflect.InvocationHandler
import java.lang.reflect.Method
import java.lang.reflect.Proxy
import java.util.*
import kotlin.reflect.KClass
import kotlin.reflect.memberFunctions
import kotlin.reflect.memberProperties
import kotlin.reflect.*
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
@@ -56,12 +54,12 @@ annotation class AcceptedAnnotations(vararg val supportedAnnotationClasses: KCla
data class KotlinScriptDefinitionFromTemplate(val template: KClass<out Any>, val environment: Map<String, Any?>?) : KotlinScriptDefinition {
private val definitionAnnotation by lazy { template.annotations.mapNotNull { it as? ScriptTemplateDefinition }.firstOrNull() }
private val definitionAnnotation by lazy { template.annotations.firstIsInstanceOrNull<ScriptTemplateDefinition>() }
override val name = template.simpleName!!
override fun getScriptParameters(scriptDescriptor: ScriptDescriptor): List<ScriptParameter> =
template.constructors.first().parameters.map { ScriptParameter(Name.identifier(it.name!!), getKotlinTypeByFqName(scriptDescriptor, it.type.toString())) }
template.primaryConstructor!!.parameters.map { ScriptParameter(Name.identifier(it.name!!), getKotlinTypeByFqName(scriptDescriptor, it.type.toString())) }
override fun getScriptSupertypes(scriptDescriptor: ScriptDescriptor): List<KotlinType> =
listOf(getKotlinTypeByFqName(scriptDescriptor, template.qualifiedName!!))
@@ -76,14 +74,19 @@ data class KotlinScriptDefinitionFromTemplate(val template: KClass<out Any>, val
override fun getScriptName(script: KtScript): Name = ScriptNameUtil.fileNameWithExtensionStripped(script, KotlinParserDefinition.STD_SCRIPT_EXT)
private class ResolverWithAcceptedAnnotations(val resolverClass: KClass<out ScriptDependenciesResolver>) {
val resolver by lazy { resolverClass.constructors.first().call() }
val resolver by lazy { resolverClass.primaryConstructor!!.call() }
val acceptedAnnotations by lazy {
val resolveMethodName = ScriptDependenciesResolver::class.memberFunctions.first().name
resolverClass.memberFunctions.find { it.name == resolveMethodName }?.annotations
?.mapNotNull { it as? AcceptedAnnotations }
?.flatMap {
val resolveMethod = ScriptDependenciesResolver::resolve
val resolverMethodAnnotations =
resolverClass.memberFunctions.find {
it.name == resolveMethod.name &&
sameSignature(it, resolveMethod)
}
?.annotations
?.filterIsInstance<AcceptedAnnotations>()
resolverMethodAnnotations?.flatMap {
val v = it.supportedAnnotationClasses
v.toList() // TODO: find out why if inlined, it fails with "java.lang.Class cannot be cast to kotlin.reflect.KClass"
v.toList() // TODO: inline after KT-9453 is resolved (now it fails with "java.lang.Class cannot be cast to kotlin.reflect.KClass")
}
?: emptyList()
}
@@ -103,23 +106,24 @@ data class KotlinScriptDefinitionFromTemplate(val template: KClass<out Any>, val
.map { KtAnnotationWrapper(it) }
.mapNotNull { wrappedAnn ->
// TODO: consider advanced matching using semantic similar to actual resolving
supportedAnnotationClasses.find { wrappedAnn.name == it.simpleName || wrappedAnn.name == it.qualifiedName }
?.let { Pair(it, wrappedAnn) }
supportedAnnotationClasses.find {
wrappedAnn.name == it.simpleName || wrappedAnn.name == it.qualifiedName
}?.let { it to wrappedAnn }
}
val annotations = fileAnnotations.map {
val annotations = fileAnnotations.map { annClassToWrapper ->
try {
val handler = AnnProxyInvocationHandler(it.first, it.second.valueArguments)
val proxy = Proxy.newProxyInstance((template as Any).javaClass.classLoader, arrayOf(it.first.java), handler) as Annotation
Pair(it.first, proxy)
val handler = AnnProxyInvocationHandler(annClassToWrapper.first, annClassToWrapper.second.valueArguments)
val proxy = Proxy.newProxyInstance((template as Any).javaClass.classLoader, arrayOf(annClassToWrapper.first.java), handler) as Annotation
annClassToWrapper.first to proxy
}
catch (ex: Exception) {
Pair(it.first, InvalidScriptResolverAnnotation(it.second.name, it.second.valueArguments, ex))
annClassToWrapper.first to InvalidScriptResolverAnnotation(annClassToWrapper.second.name, annClassToWrapper.second.valueArguments, ex)
}
}
val fileDeps = dependenciesResolvers.mapNotNull { resolverWrapper ->
val supportedAnnotations = annotations.mapNotNull { ann ->
val annFQN = ann.first.qualifiedName
if (resolverWrapper.acceptedAnnotations.any { it.qualifiedName == annFQN }) ann.second else null
val supportedAnnotations = annotations.mapNotNull { annClassToWrapper ->
val annFQN = annClassToWrapper.first.qualifiedName
if (resolverWrapper.acceptedAnnotations.any { it.qualifiedName == annFQN }) annClassToWrapper.second else null
}
resolverWrapper.resolver.resolve(getFile(file), supportedAnnotations, environment, previousDependencies)
}
@@ -148,7 +152,6 @@ data class KotlinScriptDefinitionFromTemplate(val template: KClass<out Any>, val
}
}
@Suppress("unused")
class InvalidScriptResolverAnnotation(val name: String, val params: Iterable<Any?>, val error: Exception? = null) : Annotation
class AnnProxyInvocationHandler<out K: KClass<out Any>>(val targetAnnClass: K, val annParams: List<Pair<String?, Any?>>) : InvocationHandler {
@@ -161,4 +164,12 @@ class AnnProxyInvocationHandler<out K: KClass<out Any>>(val targetAnnClass: K, v
}
return null
}
}
}
internal fun sameSignature(left: KFunction<*>, right: KFunction<*>): Boolean =
left.parameters.size == right.parameters.size &&
left.parameters.zip(right.parameters).all {
it.first.kind == KParameter.Kind.INSTANCE ||
it.first.type == it.second.type
}
@@ -42,24 +42,24 @@ interface ScriptTemplateProvider {
}
fun makeScriptDefsFromTemplateProviderExtensions(project: Project,
errorsHandler: ((ScriptTemplateProvider, Exception) -> Unit) = fun (ep, ex) = println(ex)): List<KotlinScriptDefinitionFromTemplate> =
errorsHandler: ((ScriptTemplateProvider, Exception) -> Unit) = { ep, ex -> throw ex }): List<KotlinScriptDefinitionFromTemplate> =
makeScriptDefsFromTemplateProviders(Extensions.getArea(project).getExtensionPoint(ScriptTemplateProvider.EP_NAME).extensions.asIterable(),
errorsHandler)
fun makeScriptDefsFromTemplateProviders(providers: Iterable<ScriptTemplateProvider>,
errorsHandler: ((ScriptTemplateProvider, Exception) -> Unit) = fun (ep, ex) = println(ex)
errorsHandler: ((ScriptTemplateProvider, Exception) -> Unit) = { ep, ex -> throw ex }
): List<KotlinScriptDefinitionFromTemplate> {
val idToVersion = hashMapOf<String, Int>()
return providers.filter { it.isValid }.sortedByDescending { it.version }.mapNotNull {
return providers.filter { it.isValid }.sortedByDescending { it.version }.mapNotNull { provider ->
try {
idToVersion.get(it.id)?.let { ver -> errorsHandler(it, RuntimeException("Conflicting scriptTemplateProvider ${it.id}, using one with version $ver")) }
val loader = URLClassLoader(it.dependenciesClasspath.map { File(it).toURI().toURL() }.toTypedArray(), ScriptTemplateProvider::class.java.classLoader)
val cl = loader.loadClass(it.templateClassName)
idToVersion.put(it.id, it.version)
KotlinScriptDefinitionFromTemplate(cl.kotlin, it.environment)
idToVersion.get(provider.id)?.let { ver -> errorsHandler(provider, RuntimeException("Conflicting scriptTemplateProvider ${provider.id}, using one with version $ver")) }
val loader = URLClassLoader(provider.dependenciesClasspath.map { File(it).toURI().toURL() }.toTypedArray(), ScriptTemplateProvider::class.java.classLoader)
val cl = loader.loadClass(provider.templateClassName)
idToVersion.put(provider.id, provider.version)
KotlinScriptDefinitionFromTemplate(cl.kotlin, provider.environment)
}
catch (ex: Exception) {
errorsHandler(it, ex)
errorsHandler(provider, ex)
null
}
}
@@ -36,6 +36,9 @@ import java.io.File
import java.net.URLClassLoader
import kotlin.reflect.KClass
// TODO: the contetnts of this file should go into ScriptTest.kt and replace appropriate xml-based functionality,
// as soon as the the latter is removed from the codebase
class ScriptTest2 {
@Test
fun testScriptWithParam() {
+1 -1
View File
@@ -14,6 +14,6 @@
<orderEntry type="module" module-name="light-classes" />
<orderEntry type="module" module-name="frontend.java" />
<orderEntry type="module" module-name="util" />
<orderEntry type="library" name="gradle-and-groovy-plugin" level="project" />
<orderEntry type="library" scope="PROVIDED" name="gradle-and-groovy-plugin" level="project" />
</component>
</module>
@@ -114,7 +114,7 @@ class KotlinScriptConfigurationManager(
}
private fun reloadScriptDefinitions() {
(makeScriptDefsFromTemplateProviderExtensions(project /* TODO: add logging here */) +
(makeScriptDefsFromTemplateProviderExtensions(project, { ep, ex -> /* TODO: add logging here */ }) +
loadScriptConfigsFromProjectRoot(File(project.basePath ?: "")).map { KotlinConfigurableScriptDefinition(it, kotlinEnvVars) }).let {
if (it.isNotEmpty()) {
scriptDefinitionProvider.setScriptDefinitions(it + StandardScriptDefinition)
@@ -35,7 +35,7 @@ class GradleScriptTemplateProvider(project: Project, gim: GradleInstallationMana
gradleLibsPath?.listFiles { file -> file.extension == "jar" && depLibsPrefixes.any { file.name.startsWith(it) } }
?.map { it.canonicalPath }
?: emptyList()
override val environment: Map<String, Any?>? = kotlin.collections.mapOf("gradleHome" to gradleHome)
override val environment: Map<String, Any?>? = mapOf("gradleHome" to gradleHome)
companion object {
private val depLibsPrefixes = listOf("gradle-script-kotlin", "gradle-core")