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 } 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) data class ScriptParameter(val name: Name, val type: KotlinType)
fun <TF> getFileName(file: TF): String = when (file) { fun <TF> getFileName(file: TF): String = when (file) {
@@ -24,6 +24,7 @@ import kotlin.concurrent.read
import kotlin.concurrent.write import kotlin.concurrent.write
class KotlinScriptExternalImportsProvider(val project: Project, private val scriptDefinitionProvider: KotlinScriptDefinitionProvider) { class KotlinScriptExternalImportsProvider(val project: Project, private val scriptDefinitionProvider: KotlinScriptDefinitionProvider) {
private val cacheLock = ReentrantReadWriteLock() private val cacheLock = ReentrantReadWriteLock()
private val cache = hashMapOf<String, KotlinScriptExternalDependencies>() private val cache = hashMapOf<String, KotlinScriptExternalDependencies>()
private val cacheOfNulls = hashSetOf<String>() private val cacheOfNulls = hashSetOf<String>()
@@ -82,7 +83,7 @@ class KotlinScriptExternalImportsProvider(val project: Project, private val scri
val deps = scriptDef.getDependenciesFor(file, project, oldDeps) val deps = scriptDef.getDependenciesFor(file, project, oldDeps)
when { when {
deps != null && (oldDeps == null || 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 // changed or new
cache.put(path, deps) cache.put(path, deps)
cacheOfNulls.remove(path) cacheOfNulls.remove(path)
@@ -104,9 +105,8 @@ class KotlinScriptExternalImportsProvider(val project: Project, private val scri
fun invalidateCaches() { fun invalidateCaches() {
cacheLock.write { 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? = fun getInstance(project: Project): KotlinScriptExternalImportsProvider? =
ServiceManager.getService(project, KotlinScriptExternalImportsProvider::class.java) 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 package org.jetbrains.kotlin.script
import com.intellij.openapi.util.text.StringUtil import org.jetbrains.kotlin.builtins.DefaultBuiltIns
import org.jetbrains.kotlin.psi.* 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) { internal class KtAnnotationWrapper(val psi: KtAnnotationEntry) {
val name: String val name: String
get() = (psi.typeReference?.typeElement as? KtUserType)?.referencedName.orAnonymous() get() = (psi.typeReference?.typeElement as? KtUserType)?.referencedName.orAnonymous()
val valueArguments by lazy { val valueArguments: List<Pair<String?, Any?>> by lazy {
psi.valueArguments.map { 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 { internal fun String?.orAnonymous(kind: String = ""): String =
return this ?: "<anonymous" + (if (kind.isNotBlank()) " $kind" else "") + ">" 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
}
} }
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.KtFile
import org.jetbrains.kotlin.psi.KtScript import org.jetbrains.kotlin.psi.KtScript
import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import java.io.File import java.io.File
import java.lang.reflect.InvocationHandler import java.lang.reflect.InvocationHandler
import java.lang.reflect.Method import java.lang.reflect.Method
import java.lang.reflect.Proxy import java.lang.reflect.Proxy
import java.util.* import kotlin.reflect.*
import kotlin.reflect.KClass
import kotlin.reflect.memberFunctions
import kotlin.reflect.memberProperties
@Target(AnnotationTarget.CLASS) @Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME) @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 { 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 val name = template.simpleName!!
override fun getScriptParameters(scriptDescriptor: ScriptDescriptor): List<ScriptParameter> = 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> = override fun getScriptSupertypes(scriptDescriptor: ScriptDescriptor): List<KotlinType> =
listOf(getKotlinTypeByFqName(scriptDescriptor, template.qualifiedName!!)) 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) override fun getScriptName(script: KtScript): Name = ScriptNameUtil.fileNameWithExtensionStripped(script, KotlinParserDefinition.STD_SCRIPT_EXT)
private class ResolverWithAcceptedAnnotations(val resolverClass: KClass<out ScriptDependenciesResolver>) { 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 acceptedAnnotations by lazy {
val resolveMethodName = ScriptDependenciesResolver::class.memberFunctions.first().name val resolveMethod = ScriptDependenciesResolver::resolve
resolverClass.memberFunctions.find { it.name == resolveMethodName }?.annotations val resolverMethodAnnotations =
?.mapNotNull { it as? AcceptedAnnotations } resolverClass.memberFunctions.find {
?.flatMap { it.name == resolveMethod.name &&
sameSignature(it, resolveMethod)
}
?.annotations
?.filterIsInstance<AcceptedAnnotations>()
resolverMethodAnnotations?.flatMap {
val v = it.supportedAnnotationClasses 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() ?: emptyList()
} }
@@ -103,23 +106,24 @@ data class KotlinScriptDefinitionFromTemplate(val template: KClass<out Any>, val
.map { KtAnnotationWrapper(it) } .map { KtAnnotationWrapper(it) }
.mapNotNull { wrappedAnn -> .mapNotNull { wrappedAnn ->
// TODO: consider advanced matching using semantic similar to actual resolving // TODO: consider advanced matching using semantic similar to actual resolving
supportedAnnotationClasses.find { wrappedAnn.name == it.simpleName || wrappedAnn.name == it.qualifiedName } supportedAnnotationClasses.find {
?.let { Pair(it, wrappedAnn) } wrappedAnn.name == it.simpleName || wrappedAnn.name == it.qualifiedName
}?.let { it to wrappedAnn }
} }
val annotations = fileAnnotations.map { val annotations = fileAnnotations.map { annClassToWrapper ->
try { try {
val handler = AnnProxyInvocationHandler(it.first, it.second.valueArguments) val handler = AnnProxyInvocationHandler(annClassToWrapper.first, annClassToWrapper.second.valueArguments)
val proxy = Proxy.newProxyInstance((template as Any).javaClass.classLoader, arrayOf(it.first.java), handler) as Annotation val proxy = Proxy.newProxyInstance((template as Any).javaClass.classLoader, arrayOf(annClassToWrapper.first.java), handler) as Annotation
Pair(it.first, proxy) annClassToWrapper.first to proxy
} }
catch (ex: Exception) { 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 fileDeps = dependenciesResolvers.mapNotNull { resolverWrapper ->
val supportedAnnotations = annotations.mapNotNull { ann -> val supportedAnnotations = annotations.mapNotNull { annClassToWrapper ->
val annFQN = ann.first.qualifiedName val annFQN = annClassToWrapper.first.qualifiedName
if (resolverWrapper.acceptedAnnotations.any { it.qualifiedName == annFQN }) ann.second else null if (resolverWrapper.acceptedAnnotations.any { it.qualifiedName == annFQN }) annClassToWrapper.second else null
} }
resolverWrapper.resolver.resolve(getFile(file), supportedAnnotations, environment, previousDependencies) 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 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 { 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 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, 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(), makeScriptDefsFromTemplateProviders(Extensions.getArea(project).getExtensionPoint(ScriptTemplateProvider.EP_NAME).extensions.asIterable(),
errorsHandler) errorsHandler)
fun makeScriptDefsFromTemplateProviders(providers: Iterable<ScriptTemplateProvider>, fun makeScriptDefsFromTemplateProviders(providers: Iterable<ScriptTemplateProvider>,
errorsHandler: ((ScriptTemplateProvider, Exception) -> Unit) = fun (ep, ex) = println(ex) errorsHandler: ((ScriptTemplateProvider, Exception) -> Unit) = { ep, ex -> throw ex }
): List<KotlinScriptDefinitionFromTemplate> { ): List<KotlinScriptDefinitionFromTemplate> {
val idToVersion = hashMapOf<String, Int>() 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 { try {
idToVersion.get(it.id)?.let { ver -> errorsHandler(it, RuntimeException("Conflicting scriptTemplateProvider ${it.id}, using one with version $ver")) } idToVersion.get(provider.id)?.let { ver -> errorsHandler(provider, RuntimeException("Conflicting scriptTemplateProvider ${provider.id}, using one with version $ver")) }
val loader = URLClassLoader(it.dependenciesClasspath.map { File(it).toURI().toURL() }.toTypedArray(), ScriptTemplateProvider::class.java.classLoader) val loader = URLClassLoader(provider.dependenciesClasspath.map { File(it).toURI().toURL() }.toTypedArray(), ScriptTemplateProvider::class.java.classLoader)
val cl = loader.loadClass(it.templateClassName) val cl = loader.loadClass(provider.templateClassName)
idToVersion.put(it.id, it.version) idToVersion.put(provider.id, provider.version)
KotlinScriptDefinitionFromTemplate(cl.kotlin, it.environment) KotlinScriptDefinitionFromTemplate(cl.kotlin, provider.environment)
} }
catch (ex: Exception) { catch (ex: Exception) {
errorsHandler(it, ex) errorsHandler(provider, ex)
null null
} }
} }
@@ -36,6 +36,9 @@ import java.io.File
import java.net.URLClassLoader import java.net.URLClassLoader
import kotlin.reflect.KClass 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 { class ScriptTest2 {
@Test @Test
fun testScriptWithParam() { fun testScriptWithParam() {
+1 -1
View File
@@ -14,6 +14,6 @@
<orderEntry type="module" module-name="light-classes" /> <orderEntry type="module" module-name="light-classes" />
<orderEntry type="module" module-name="frontend.java" /> <orderEntry type="module" module-name="frontend.java" />
<orderEntry type="module" module-name="util" /> <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> </component>
</module> </module>
@@ -114,7 +114,7 @@ class KotlinScriptConfigurationManager(
} }
private fun reloadScriptDefinitions() { 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 { loadScriptConfigsFromProjectRoot(File(project.basePath ?: "")).map { KotlinConfigurableScriptDefinition(it, kotlinEnvVars) }).let {
if (it.isNotEmpty()) { if (it.isNotEmpty()) {
scriptDefinitionProvider.setScriptDefinitions(it + StandardScriptDefinition) 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) } } gradleLibsPath?.listFiles { file -> file.extension == "jar" && depLibsPrefixes.any { file.name.startsWith(it) } }
?.map { it.canonicalPath } ?.map { it.canonicalPath }
?: emptyList() ?: 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 { companion object {
private val depLibsPrefixes = listOf("gradle-script-kotlin", "gradle-core") private val depLibsPrefixes = listOf("gradle-script-kotlin", "gradle-core")