org.jetbrains.kotlin.script.KotlinScriptDefinitionFromAnnotatedTemplate.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of kotlin-compiler-embeddable Show documentation
Show all versions of kotlin-compiler-embeddable Show documentation
the Kotlin compiler embeddable
/*
* 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.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.memberFunctions
import kotlin.reflect.primaryConstructor
open class KotlinScriptDefinitionFromAnnotatedTemplate(
template: KClass,
providedResolver: ScriptDependenciesResolver? = null,
providedScriptFilePattern: String? = null,
val environment: Map? = null
) : KotlinScriptDefinition(template) {
val scriptFilePattern by lazy {
providedScriptFilePattern ?: template.annotations.firstIsInstanceOrNull()?.scriptFilePattern ?: DEFAULT_SCRIPT_FILE_PATTERN
}
val resolver: ScriptDependenciesResolver? by lazy {
val defAnn by lazy { template.annotations.firstIsInstanceOrNull() }
when {
providedResolver != null -> providedResolver
// TODO: logScriptDefMessage missing or invalid constructor
defAnn != null ->
try {
defAnn.resolver.primaryConstructor?.call() ?: null.apply {
log.warn("[kts] No default constructor found for ${defAnn.resolver.qualifiedName}")
}
}
catch (ex: ClassCastException) {
log.warn("[kts] Script def error ${ex.message}")
null
}
else -> BasicScriptDependenciesResolver()
}
}
val samWithReceiverAnnotations: List? by lazy {
template.annotations.firstIsInstanceOrNull()?.annotations?.toList()
}
private val acceptedAnnotations: List> by lazy {
val resolveMethod = ScriptDependenciesResolver::resolve
val resolverMethodAnnotations =
resolver?.let { it::class }?.memberFunctions?.find { function ->
function.name == resolveMethod.name &&
sameSignature(function, resolveMethod)
}?.annotations?.filterIsInstance()
resolverMethodAnnotations?.flatMap {
val v = it.supportedAnnotationClasses
v.toList() // TODO: inline after KT-9453 is resolved (now it fails with "java.lang.Class cannot be cast to kotlin.reflect.KClass")
}
?: emptyList()
}
override val name = template.simpleName!!
override fun isScript(file: TF): Boolean =
scriptFilePattern.let { Regex(it).matches(getFileName(file)) }
// 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 getDependenciesFor(file: TF, project: Project, previousDependencies: KotlinScriptExternalDependencies?): KotlinScriptExternalDependencies? {
fun logClassloadingError(ex: Throwable) {
logScriptDefMessage(ScriptDependenciesResolver.ReportSeverity.WARNING, ex.message ?: "Invalid script template: ${template.qualifiedName}", null)
}
fun makeScriptContents() = BasicScriptContents(file, getAnnotations = {
val classLoader = (template as Any).javaClass.classLoader
try {
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) }
}
}
catch (ex: Throwable) {
logClassloadingError(ex)
emptyList()
}
})
try {
val fileDeps = resolver?.resolve(makeScriptContents(), environment, ::logScriptDefMessage, previousDependencies)
// TODO: use it as a Future
return fileDeps?.get()
}
catch (ex: Throwable) {
logClassloadingError(ex)
}
return null
}
private fun getAnnotationEntries(file: TF, project: Project): Iterable = 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 {
val psiFile: PsiFile = PsiManager.getInstance(project).findFile(file)
?: throw IllegalArgumentException("Unable to load PSI from ${file.canonicalPath}")
return getAnnotationEntriesFromPsiFile(psiFile)
}
class BasicScriptContents(myFile: TF, getAnnotations: () -> Iterable) : ScriptContents {
override val file: File? by lazy { getFile(myFile) }
override val annotations: Iterable by lazy { getAnnotations() }
override val text: CharSequence? by lazy { getFileContents(myFile) }
}
override fun toString(): String = "KotlinScriptDefinitionFromAnnotatedTemplate - ${template.simpleName}"
override val annotationsForSamWithReceivers: List
get() = samWithReceiverAnnotations ?: super.annotationsForSamWithReceivers
companion object {
internal val log = Logger.getInstance(KotlinScriptDefinitionFromAnnotatedTemplate::class.java)
}
}
internal fun logScriptDefMessage(reportSeverity: ScriptDependenciesResolver.ReportSeverity, s: String, position: ScriptContents.Position?): Unit {
val msg = (position?.run { "[at $line:$col]" } ?: "") + s
when (reportSeverity) {
ScriptDependenciesResolver.ReportSeverity.ERROR -> KotlinScriptDefinitionFromAnnotatedTemplate.log.error(msg)
ScriptDependenciesResolver.ReportSeverity.WARNING -> KotlinScriptDefinitionFromAnnotatedTemplate.log.warn(msg)
ScriptDependenciesResolver.ReportSeverity.INFO -> KotlinScriptDefinitionFromAnnotatedTemplate.log.info(msg)
ScriptDependenciesResolver.ReportSeverity.DEBUG -> KotlinScriptDefinitionFromAnnotatedTemplate.log.debug(msg)
}
}