All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jetbrains.kotlin.script.KotlinScriptDefinitionFromAnnotatedTemplate.kt Maven / Gradle / Ivy

There is a newer version: 2.1.0-Beta1
Show newest version
/*
 * 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)
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy