Refactor getting dependencies for script files API

Deprecate KotlinScriptDefinition#getDependenciesFor
Expose dependencyResolver as a property
KotlinScriptExternalImportsProvider is the component clients should be asking for dependencies
This commit is contained in:
Pavel V. Talanov
2017-06-19 19:59:39 +03:00
parent ff391628de
commit 788eaf8a18
11 changed files with 132 additions and 101 deletions
@@ -18,17 +18,10 @@ package org.jetbrains.kotlin.script
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.StandardFileSystems
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiManager
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.name.NameUtils
import org.jetbrains.kotlin.psi.KtAnnotationEntry
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtScript
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import java.io.File
import kotlin.reflect.KClass
import kotlin.reflect.KFunction
import kotlin.reflect.KParameter
@@ -54,7 +47,7 @@ open class KotlinScriptDefinitionFromAnnotatedTemplate(
?: DEFAULT_SCRIPT_FILE_PATTERN
}
val resolver: ScriptDependenciesResolver? by lazy {
override val dependencyResolver: ScriptDependenciesResolver by lazy {
val defAnn by lazy { takeUnlessError { template.annotations.firstIsInstanceOrNull<kotlin.script.templates.ScriptTemplateDefinition>() } }
val legacyDefAnn by lazy { takeUnlessError { template.annotations.firstIsInstanceOrNull<org.jetbrains.kotlin.script.ScriptTemplateDefinition>() } }
when {
@@ -84,8 +77,8 @@ open class KotlinScriptDefinitionFromAnnotatedTemplate(
log.warn("[kts] Script def error ${ex.message}")
null
}
else -> BasicScriptDependenciesResolver()
}
else -> null
} ?: BasicScriptDependenciesResolver()
}
val samWithReceiverAnnotations: List<String>? by lazy {
@@ -93,7 +86,7 @@ open class KotlinScriptDefinitionFromAnnotatedTemplate(
?: takeUnlessError { template.annotations.firstIsInstanceOrNull<org.jetbrains.kotlin.script.SamWithReceiverAnnotations>()?.annotations?.toList() }
}
private val acceptedAnnotations: List<KClass<out Annotation>> by lazy {
override val acceptedAnnotations: List<KClass<out Annotation>> by lazy {
fun sameSignature(left: KFunction<*>, right: KFunction<*>): Boolean =
left.parameters.size == right.parameters.size &&
@@ -104,7 +97,7 @@ open class KotlinScriptDefinitionFromAnnotatedTemplate(
val resolveMethod = ScriptDependenciesResolver::resolve
val resolverMethodAnnotations =
resolver?.let { it::class }?.memberFunctions?.find { function ->
dependencyResolver::class.memberFunctions.find { function ->
function.name == resolveMethod.name &&
sameSignature(function, resolveMethod)
}?.annotations?.filterIsInstance<AcceptedAnnotations>()
@@ -122,62 +115,7 @@ open class KotlinScriptDefinitionFromAnnotatedTemplate(
// TODO: implement other strategy - e.g. try to extract something from match with ScriptFilePattern
override fun getScriptName(script: KtScript): Name = NameUtils.getScriptNameForFile(script.containingKtFile.name)
override fun <TF: Any> getDependenciesFor(file: TF, project: Project, previousDependencies: KotlinScriptExternalDependencies?): KotlinScriptExternalDependencies? {
return takeUnlessError(reportError = false) {
val fileDeps = resolver?.resolve(makeScriptContents(file, project), environment, ::logScriptDefMessage, previousDependencies)
// TODO: use it as a Future
fileDeps?.get()
}
}
fun <TF: Any> makeScriptContents(file: TF, project: Project) = BasicScriptContents(file, getAnnotations = {
val classLoader = template.java.classLoader
takeUnlessError(reportError = false) {
getAnnotationEntries(file, project)
.mapNotNull { psiAnn ->
// TODO: consider advanced matching using semantic similar to actual resolving
acceptedAnnotations.find { ann ->
psiAnn.typeName.let { it == ann.simpleName || it == ann.qualifiedName }
}?.let { constructAnnotation(psiAnn, classLoader.loadClass(it.qualifiedName).kotlin as KClass<out Annotation>) }
}
}
?: emptyList()
})
fun getDependenciesFor(previousDependencies: KotlinScriptExternalDependencies?, scriptContents: ScriptContents): KotlinScriptExternalDependencies? {
return takeUnlessError(reportError = false) {
// TODO: use it as a Future
resolver?.resolve(scriptContents, environment, ::logScriptDefMessage, previousDependencies)?.get()
}
}
private fun <TF: Any> getAnnotationEntries(file: TF, project: Project): Iterable<KtAnnotationEntry> = when (file) {
is PsiFile -> getAnnotationEntriesFromPsiFile(file)
is VirtualFile -> getAnnotationEntriesFromVirtualFile(file, project)
is File -> {
val virtualFile = (StandardFileSystems.local().findFileByPath(file.canonicalPath)
?: throw IllegalArgumentException("Unable to find file ${file.canonicalPath}"))
getAnnotationEntriesFromVirtualFile(virtualFile, project)
}
else -> throw IllegalArgumentException("Unsupported file type $file")
}
private fun getAnnotationEntriesFromPsiFile(file: PsiFile) =
(file as? KtFile)?.annotationEntries
?: throw IllegalArgumentException("Unable to extract kotlin annotations from ${file.name} (${file.fileType})")
private fun getAnnotationEntriesFromVirtualFile(file: VirtualFile, project: Project): Iterable<KtAnnotationEntry> {
val psiFile: PsiFile = PsiManager.getInstance(project).findFile(file)
?: throw IllegalArgumentException("Unable to load PSI from ${file.canonicalPath}")
return getAnnotationEntriesFromPsiFile(psiFile)
}
class BasicScriptContents<out TF: Any>(myFile: TF, getAnnotations: () -> Iterable<Annotation>) : ScriptContents {
override val file: File? by lazy { getFile(myFile) }
override val annotations: Iterable<Annotation> by lazy { getAnnotations() }
override val text: CharSequence? by lazy { getFileContents(myFile) }
}
override fun <TF: Any> getDependenciesFor(file: TF, project: Project, previousDependencies: KotlinScriptExternalDependencies?) = error("Should not be called")
override fun toString(): String = "KotlinScriptDefinitionFromAnnotatedTemplate - ${template.simpleName}"
@@ -0,0 +1,88 @@
/*
* Copyright 2010-2017 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.script
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.StandardFileSystems
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiManager
import org.jetbrains.kotlin.psi.KtAnnotationEntry
import org.jetbrains.kotlin.psi.KtFile
import java.io.File
import kotlin.reflect.KClass
import kotlin.script.dependencies.KotlinScriptExternalDependencies
import kotlin.script.dependencies.ScriptContents
abstract class KotlinScriptExternalImportsProviderBase(private val project: Project): KotlinScriptExternalImportsProvider {
fun <TF : Any> getScriptContents(scriptDefinition: KotlinScriptDefinition, file: TF)
= BasicScriptContents(file, getAnnotations = { loadAnnotations(scriptDefinition, file) })
private fun <TF : Any> loadAnnotations(scriptDefinition: KotlinScriptDefinition, file: TF): List<Annotation> {
val classLoader = scriptDefinition.template.java.classLoader
// TODO_R: report error on failure to load annotation class
return getAnnotationEntries(file, project)
.mapNotNull { psiAnn ->
// TODO: consider advanced matching using semantic similar to actual resolving
scriptDefinition.acceptedAnnotations.find { ann ->
psiAnn.typeName.let { it == ann.simpleName || it == ann.qualifiedName }
}?.let { constructAnnotation(psiAnn, classLoader.loadClass(it.qualifiedName).kotlin as KClass<out Annotation>) }
}
}
private fun <TF: Any> getAnnotationEntries(file: TF, project: Project): Iterable<KtAnnotationEntry> = when (file) {
is PsiFile -> getAnnotationEntriesFromPsiFile(file)
is VirtualFile -> getAnnotationEntriesFromVirtualFile(file, project)
is File -> {
val virtualFile = (StandardFileSystems.local().findFileByPath(file.canonicalPath)
?: throw IllegalArgumentException("Unable to find file ${file.canonicalPath}"))
getAnnotationEntriesFromVirtualFile(virtualFile, project)
}
else -> throw IllegalArgumentException("Unsupported file type $file")
}
private fun getAnnotationEntriesFromPsiFile(file: PsiFile) =
(file as? KtFile)?.annotationEntries
?: throw IllegalArgumentException("Unable to extract kotlin annotations from ${file.name} (${file.fileType})")
private fun getAnnotationEntriesFromVirtualFile(file: VirtualFile, project: Project): Iterable<KtAnnotationEntry> {
val psiFile: PsiFile = PsiManager.getInstance(project).findFile(file)
?: throw IllegalArgumentException("Unable to load PSI from ${file.canonicalPath}")
return getAnnotationEntriesFromPsiFile(psiFile)
}
class BasicScriptContents<out TF: Any>(myFile: TF, getAnnotations: () -> Iterable<Annotation>) : ScriptContents {
override val file: File? by lazy { getFile(myFile) }
override val annotations: Iterable<Annotation> by lazy { getAnnotations() }
override val text: CharSequence? by lazy { getFileContents(myFile) }
}
fun <TF: Any> resolveDependencies(
scriptDef: KotlinScriptDefinition,
file: TF,
oldDependencies: KotlinScriptExternalDependencies? = null
): KotlinScriptExternalDependencies? {
val scriptContents = getScriptContents(scriptDef, file)
return scriptDef.dependencyResolver.resolve(
scriptContents,
(scriptDef as? KotlinScriptDefinitionFromAnnotatedTemplate)?.environment,
{ _, _, _ -> Unit }, // TODO_R:
oldDependencies
).get()
}
}
@@ -16,7 +16,6 @@
package org.jetbrains.kotlin.script
import com.intellij.openapi.components.ServiceManager
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.project.Project
import java.io.File
@@ -28,22 +27,24 @@ import kotlin.script.dependencies.KotlinScriptExternalDependencies
class KotlinScriptExternalImportsProviderImpl(
val project: Project,
private val scriptDefinitionProvider: KotlinScriptDefinitionProvider
) : KotlinScriptExternalImportsProvider {
) : KotlinScriptExternalImportsProviderBase(project) {
private val cacheLock = ReentrantReadWriteLock()
private val cache = hashMapOf<String, KotlinScriptExternalDependencies?>()
override fun <TF: Any> getExternalImports(file: TF): KotlinScriptExternalDependencies? = cacheLock.read {
override fun <TF : Any> getScriptDependencies(file: TF): KotlinScriptExternalDependencies? = cacheLock.read {
calculateExternalDependencies(file)
}
private fun <TF: Any> calculateExternalDependencies(file: TF): KotlinScriptExternalDependencies? {
private fun <TF : Any> calculateExternalDependencies(file: TF): KotlinScriptExternalDependencies? {
val path = getFilePath(file)
val cached = cache[path]
return if (cached != null) cached else {
return if (cached != null) cached
else {
val scriptDef = scriptDefinitionProvider.findScriptDefinition(file)
if (scriptDef != null) {
val deps = scriptDef.getDependenciesFor(file, project, null)
val deps = resolveDependencies(scriptDef, file)
if (deps != null) {
log.info("[kts] new cached deps for $path: ${deps.classpath.joinToString(File.pathSeparator)}")
}
@@ -55,11 +56,6 @@ class KotlinScriptExternalImportsProviderImpl(
else null
}
}
companion object {
@JvmStatic
fun getInstance(project: Project): KotlinScriptExternalImportsProvider? =
ServiceManager.getService(project, KotlinScriptExternalImportsProvider::class.java)
internal val log = Logger.getInstance(KotlinScriptExternalImportsProvider::class.java)
}
}
private val log = Logger.getInstance(KotlinScriptExternalImportsProvider::class.java)