Move some script related part of frontend to separate module to avoid using kotlin-reflect.jar in frontend module

Main goal is get rid of kotlin-reflect.jar from modules what required for minimal compiler.jar which can compile Kotlin only to JS to make it smaller.
This commit is contained in:
Zalim Bashorov
2017-04-13 22:07:18 +03:00
parent 05c3e7e81d
commit 51e84f7ce4
27 changed files with 175 additions and 35 deletions
@@ -0,0 +1,211 @@
/*
* 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.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
import kotlin.reflect.full.memberFunctions
import kotlin.reflect.full.primaryConstructor
import kotlin.script.dependencies.BasicScriptDependenciesResolver
import kotlin.script.dependencies.KotlinScriptExternalDependencies
import kotlin.script.dependencies.ScriptContents
import kotlin.script.dependencies.ScriptDependenciesResolver
import kotlin.script.templates.AcceptedAnnotations
open class KotlinScriptDefinitionFromAnnotatedTemplate(
template: KClass<out Any>,
providedResolver: ScriptDependenciesResolver? = null,
providedScriptFilePattern: String? = null,
val environment: Map<String, Any?>? = null
) : KotlinScriptDefinition(template) {
val scriptFilePattern by lazy {
providedScriptFilePattern
?: template.annotations.firstIsInstanceOrNull<kotlin.script.templates.ScriptTemplateDefinition>()?.scriptFilePattern
?: template.annotations.firstIsInstanceOrNull<org.jetbrains.kotlin.script.ScriptTemplateDefinition>()?.scriptFilePattern
?: DEFAULT_SCRIPT_FILE_PATTERN
}
val resolver: ScriptDependenciesResolver? by lazy {
val defAnn by lazy { template.annotations.firstIsInstanceOrNull<kotlin.script.templates.ScriptTemplateDefinition>() }
val legacyDefAnn by lazy { template.annotations.firstIsInstanceOrNull<org.jetbrains.kotlin.script.ScriptTemplateDefinition>() }
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
}
legacyDefAnn != null ->
try {
log.warn("[kts] Deprecated annotations on the script template are used, please update the provider")
legacyDefAnn.resolver.primaryConstructor?.call()?.let {
LegacyScriptDependenciesResolverWrapper(it)
}
?: null.apply {
log.warn("[kts] No default constructor found for ${legacyDefAnn.resolver.qualifiedName}")
}
}
catch (ex: ClassCastException) {
log.warn("[kts] Script def error ${ex.message}")
null
}
else -> BasicScriptDependenciesResolver()
}
}
val samWithReceiverAnnotations: List<String>? by lazy {
template.annotations.firstIsInstanceOrNull<kotlin.script.extensions.SamWithReceiverAnnotations>()?.annotations?.toList()
?: template.annotations.firstIsInstanceOrNull<org.jetbrains.kotlin.script.SamWithReceiverAnnotations>()?.annotations?.toList()
}
private val acceptedAnnotations: List<KClass<out Annotation>> by lazy {
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
}
val resolveMethod = ScriptDependenciesResolver::resolve
val resolverMethodAnnotations =
resolver?.let { it::class }?.memberFunctions?.find { function ->
function.name == resolveMethod.name &&
sameSignature(function, resolveMethod)
}?.annotations?.filterIsInstance<AcceptedAnnotations>()
resolverMethodAnnotations?.flatMap {
it.supportedAnnotationClasses.toList()
}
?: emptyList()
}
override val name = template.simpleName!!
override fun <TF: Any> 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 <TF: Any> 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.java.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<out Annotation>) }
}
}
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 <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 toString(): String = "KotlinScriptDefinitionFromAnnotatedTemplate - ${template.simpleName}"
override val annotationsForSamWithReceivers: List<String>
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)
}
}
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
}
@@ -0,0 +1,162 @@
/*
* 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.components.ServiceManager
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.project.Project
import java.io.File
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.read
import kotlin.concurrent.write
import kotlin.script.dependencies.KotlinScriptExternalDependencies
class KotlinScriptExternalImportsProviderImpl(
val project: Project,
private val scriptDefinitionProvider: KotlinScriptDefinitionProvider
) : KotlinScriptExternalImportsProvider {
private val cacheLock = ReentrantReadWriteLock()
private val cache = hashMapOf<String, KotlinScriptExternalDependencies?>()
override fun <TF: Any> getExternalImports(file: TF): KotlinScriptExternalDependencies? = cacheLock.read {
calculateExternalDependencies(file)
}
fun <TF: Any> getExternalImports(files: Iterable<TF>): List<KotlinScriptExternalDependencies> = cacheLock.read {
files.mapNotNull { calculateExternalDependencies(it) }
}
private fun <TF: Any> calculateExternalDependencies(file: TF): KotlinScriptExternalDependencies? {
val path = getFilePath(file)
val cached = cache[path]
return if (cached != null) cached else {
val scriptDef = scriptDefinitionProvider.findScriptDefinition(file)
if (scriptDef != null) {
val deps = scriptDef.getDependenciesFor(file, project, null)
if (deps != null) {
log.info("[kts] new cached deps for $path: ${deps.classpath.joinToString(File.pathSeparator)}")
}
cacheLock.write {
cache.put(path, deps)
}
deps
}
else null
}
}
// optimized for initial caching, additional handling of possible duplicates to save a call to distinct
// returns list of cached files
override fun <TF: Any> cacheExternalImports(files: Iterable<TF>): Iterable<TF> = cacheLock.write {
val uncached = hashSetOf<String>()
files.mapNotNull { file ->
val path = getFilePath(file)
if (isValidFile(file) && !cache.containsKey(path) && !uncached.contains(path)) {
val scriptDef = scriptDefinitionProvider.findScriptDefinition(file)
if (scriptDef != null) {
val deps = scriptDef.getDependenciesFor(file, project, null)
if (deps != null) {
log.info("[kts] cached deps for $path: ${deps.classpath.joinToString(File.pathSeparator)}")
}
cache.put(path, deps)
file
}
else {
uncached.add(path)
null
}
}
else null
}
}
// optimized for update, no special duplicates handling
// returns files with valid script definition (or deleted from cache - which in fact should have script def too)
// TODO: this is the badly designed contract, since it mixes the entities, but these files are needed on the calling site now. Find out other solution
override fun <TF: Any> updateExternalImportsCache(files: Iterable<TF>): Iterable<TF> = cacheLock.write {
files.mapNotNull { file ->
val path = getFilePath(file)
if (!isValidFile(file)) {
if (cache.remove(path) != null) {
log.debug("[kts] removed deps for file $path")
file
} // cleared
else {
null // unknown
}
}
else {
val scriptDef = scriptDefinitionProvider.findScriptDefinition(file)
if (scriptDef != null) {
val oldDeps = cache[path]
val deps = scriptDef.getDependenciesFor(file, project, oldDeps)
when {
deps != null && (oldDeps == null ||
!deps.classpath.isSamePathListAs(oldDeps.classpath) || !deps.sources.isSamePathListAs(oldDeps.sources)) -> {
// changed or new
log.info("[kts] updated/new cached deps for $path: ${deps.classpath.joinToString(File.pathSeparator)}")
cache.put(path, deps)
}
deps != null -> {
// same as before
}
else -> {
if (cache.remove(path) != null) {
log.debug("[kts] removed deps for $path")
} // cleared
}
}
file
}
else null // not a script
}
}
}
override fun invalidateCaches() {
cacheLock.write(cache::clear)
}
override fun getKnownCombinedClasspath(): List<File> = cacheLock.read {
cache.values.flatMap { it?.classpath ?: emptyList() }
}.distinct()
override fun getKnownSourceRoots(): List<File> = cacheLock.read {
cache.values.flatMap { it?.sources ?: emptyList() }
}.distinct()
override fun <TF: Any> getCombinedClasspathFor(files: Iterable<TF>): List<File> =
getExternalImports(files)
.flatMap { it.classpath }
.distinct()
companion object {
@JvmStatic
fun getInstance(project: Project): KotlinScriptExternalImportsProvider? =
ServiceManager.getService(project, KotlinScriptExternalImportsProvider::class.java)
internal val log = Logger.getInstance(KotlinScriptExternalImportsProvider::class.java)
}
}
private fun Iterable<File>.isSamePathListAs(other: Iterable<File>): Boolean =
with (Pair(iterator(), other.iterator())) {
while (first.hasNext() && second.hasNext()) {
if (first.next().canonicalPath != second.next().canonicalPath) return false
}
!(first.hasNext() || second.hasNext())
}
@@ -0,0 +1,28 @@
/*
* 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 org.jetbrains.kotlin.descriptors.ScriptDescriptor
import kotlin.reflect.KClass
class ScriptHelperImpl : ScriptHelper {
override fun getScriptParameters(kotlinScriptDefinition: KotlinScriptDefinition, scriptDefinition: ScriptDescriptor) =
kotlinScriptDefinition.getScriptParameters(scriptDefinition)
override fun getKotlinType(scriptDescriptor: ScriptDescriptor, kClass: KClass<out Any>) =
getKotlinTypeByKClass(scriptDescriptor, kClass)
}
@@ -0,0 +1,64 @@
/*
* 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 org.jetbrains.kotlin.builtins.DefaultBuiltIns
import org.jetbrains.kotlin.config.LanguageVersionSettingsImpl
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
import org.jetbrains.kotlin.utils.tryCreateCallableMappingFromNamedArgs
import kotlin.reflect.KClass
import kotlin.reflect.KParameter
import kotlin.reflect.full.primaryConstructor
internal val KtAnnotationEntry.typeName: String get() = (typeReference?.typeElement as? KtUserType)?.referencedName.orAnonymous()
internal fun String?.orAnonymous(kind: String = ""): String =
this ?: "<anonymous" + (if (kind.isNotBlank()) " $kind" else "") + ">"
internal fun constructAnnotation(psi: KtAnnotationEntry, targetClass: KClass<out Annotation>): Annotation {
val valueArguments = psi.valueArguments.map { arg ->
val evaluator = ConstantExpressionEvaluator(DefaultBuiltIns.Instance, LanguageVersionSettingsImpl.DEFAULT)
val trace = BindingTraceContext()
val result = evaluator.evaluateToConstantValue(arg.getArgumentExpression()!!, trace, TypeUtils.NO_EXPECTED_TYPE)
// TODO: consider inspecting `trace` to find diagnostics reported during the computation (such as division by zero, integer overflow, invalid annotation parameters etc.)
val argName = arg.getArgumentName()?.asName?.toString()
argName to result?.value
}
val mappedArguments: Map<KParameter, Any?> =
tryCreateCallableMappingFromNamedArgs(targetClass.constructors.first(), valueArguments)
?: return InvalidScriptResolverAnnotation(psi.typeName, valueArguments)
try {
return targetClass.primaryConstructor!!.callBy(mappedArguments)
}
catch (ex: Exception) {
return InvalidScriptResolverAnnotation(psi.typeName, valueArguments, ex)
}
}
// NOTE: this class is used for error reporting. But in order to pass plugin verification, it should derive directly from java's Annotation
// and implement annotationType method (see #KT-16621 for details).
// TODO: instead of the workaround described above, consider using a sum-type for returning errors from constructAnnotation
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
class InvalidScriptResolverAnnotation(val name: String, val annParams: List<Pair<String?, Any?>>?, val error: Exception? = null) : Annotation, java.lang.annotation.Annotation {
override fun annotationType(): Class<out Annotation> = InvalidScriptResolverAnnotation::class.java
}
@@ -0,0 +1,145 @@
/*
* 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 java.io.File
import java.util.concurrent.Future
import java.util.concurrent.TimeUnit
import kotlin.reflect.KClass
const val DEFAULT_SCRIPT_FILE_PATTERN = ".*\\.kts"
// TODO: remove this file and all the usages after releasing GSK 1.0
@Deprecated("Used only for compatibility with legacy code, use kotlin.script.templatesScriptTemplateDefinition instead",
replaceWith = ReplaceWith("kotlin.script.templates.ScriptTemplateDefinition"))
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class ScriptTemplateDefinition(val resolver: KClass<out ScriptDependenciesResolver>,
val scriptFilePattern: String = DEFAULT_SCRIPT_FILE_PATTERN)
@Deprecated("Used only for compatibility with legacy code, use kotlin.script.extensions.SamWithReceiverAnnotations instead",
replaceWith = ReplaceWith("kotlin.script.extensions.SamWithReceiverAnnotations"))
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class SamWithReceiverAnnotations(vararg val annotations: String)
@Deprecated("Used only for compatibility with legacy code, use kotlin.script.dependencies.ScriptContents instead",
replaceWith = ReplaceWith("kotlin.script.dependencies.ScriptContents"))
interface ScriptContents {
data class Position(val line: Int, val col: Int)
val file: File?
val annotations: Iterable<Annotation>
val text: CharSequence?
}
@Deprecated("Used only for compatibility with legacy code, use kotlin.script.dependencies.PseudoFuture instead",
replaceWith = ReplaceWith("kotlin.script.dependencies.PseudoFuture"))
class PseudoFuture<T>(private val value: T): Future<T> {
override fun get(): T = value
override fun get(p0: Long, p1: TimeUnit): T = value
override fun cancel(p0: Boolean): Boolean = false
override fun isDone(): Boolean = true
override fun isCancelled(): Boolean = false
}
fun KotlinScriptExternalDependencies?.asFuture(): PseudoFuture<KotlinScriptExternalDependencies?> = PseudoFuture(this)
@Deprecated("Used only for compatibility with legacy code, use kotlin.script.dependencies.ScriptDependenciesResolver instead",
replaceWith = ReplaceWith("kotlin.script.dependencies.ScriptDependenciesResolver"))
interface ScriptDependenciesResolver {
enum class ReportSeverity { ERROR, WARNING, INFO, DEBUG }
fun resolve(script: ScriptContents,
environment: Map<String, Any?>?,
report: (ReportSeverity, String, ScriptContents.Position?) -> Unit,
previousDependencies: KotlinScriptExternalDependencies?
): Future<KotlinScriptExternalDependencies?> = PseudoFuture(null)
}
@Deprecated("Used only for compatibility with legacy code, use kotlin.script.dependencies.KotlinScriptExternalDependencies instead",
replaceWith = ReplaceWith("kotlin.script.dependencies.KotlinScriptExternalDependencies"))
interface KotlinScriptExternalDependencies : Comparable<KotlinScriptExternalDependencies> {
val javaHome: String? get() = null
val classpath: Iterable<File> get() = emptyList()
val imports: Iterable<String> get() = emptyList()
val sources: Iterable<File> get() = emptyList()
val scripts: Iterable<File> get() = emptyList()
override fun compareTo(other: KotlinScriptExternalDependencies): Int =
compareValues(javaHome, other.javaHome)
.chainCompare { compareIterables(classpath, other.classpath) }
.chainCompare { compareIterables(imports, other.imports) }
.chainCompare { compareIterables(sources, other.sources) }
.chainCompare { compareIterables(scripts, other.scripts) }
}
private fun<T: Comparable<T>> compareIterables(a: Iterable<T>, b: Iterable<T>): Int {
val ia = a.iterator()
val ib = b.iterator()
while (true) {
if (ia.hasNext() && !ib.hasNext()) return 1
if (!ia.hasNext() && !ib.hasNext()) return 0
if (!ia.hasNext()) return -1
val compRes = compareValues(ia.next(), ib.next())
if (compRes != 0) return compRes
}
}
private inline fun Int.chainCompare(compFn: () -> Int ): Int = if (this != 0) this else compFn()
class LegacyScriptDependenciesResolverWrapper(val legacyResolver: ScriptDependenciesResolver) : kotlin.script.dependencies.ScriptDependenciesResolver {
override fun resolve(script: kotlin.script.dependencies.ScriptContents,
environment: Map<String, Any?>?,
report: (kotlin.script.dependencies.ScriptDependenciesResolver.ReportSeverity, String, kotlin.script.dependencies.ScriptContents.Position?) -> Unit,
previousDependencies: kotlin.script.dependencies.KotlinScriptExternalDependencies?
): Future<kotlin.script.dependencies.KotlinScriptExternalDependencies?> {
val legacyDeps = legacyResolver.resolve(
object : ScriptContents {
override val file: File? get() = script.file
override val annotations: Iterable<Annotation> get() = script.annotations
override val text: CharSequence? get() = script.text
},
environment,
{ sev, msg, pos -> report(kotlin.script.dependencies.ScriptDependenciesResolver.ReportSeverity.values()[sev.ordinal],
msg,
pos?.let { kotlin.script.dependencies.ScriptContents.Position(it.line, it.col) }) },
previousDependencies?.let {
object : KotlinScriptExternalDependencies {
override val javaHome get() = it.javaHome
override val classpath get() = it.classpath
override val imports get() = it.imports
override val sources get() = it.sources
override val scripts get() = it.scripts
}
}
).get()
return kotlin.script.dependencies.PseudoFuture(legacyDeps?.let {
object : kotlin.script.dependencies.KotlinScriptExternalDependencies {
override val javaHome get() = it.javaHome
override val classpath get() = it.classpath
override val imports get() = it.imports
override val sources get() = it.sources
override val scripts get() = it.scripts
}
})
}
}
@@ -0,0 +1,79 @@
/*
* 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.diagnostic.Logger
import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.openapi.extensions.Extensions
import com.intellij.openapi.project.Project
import java.io.File
import java.net.URLClassLoader
import kotlin.script.dependencies.ScriptDependenciesResolver
interface ScriptTemplatesProvider {
// for resolving ambiguities
val id: String
@Deprecated("Parameter isn't used for resolving priorities anymore. " +
"com.intellij.openapi.extensions.LoadingOrder constants can be used to order providers when registered from Intellij plugin.",
ReplaceWith("0"))
val version: Int get() = 0
val isValid: Boolean
val templateClassNames: Iterable<String>
val resolver: ScriptDependenciesResolver? get() = null
val filePattern: String? get() = null
val dependenciesClasspath: Iterable<String>
val environment: Map<String, Any?>?
companion object {
val EP_NAME: ExtensionPointName<ScriptTemplatesProvider> =
ExtensionPointName.create<ScriptTemplatesProvider>("org.jetbrains.kotlin.scriptTemplatesProvider")
}
}
fun makeScriptDefsFromTemplatesProviderExtensions(project: Project,
errorsHandler: ((ScriptTemplatesProvider, Exception) -> Unit) = { _, ex -> throw ex }
): List<KotlinScriptDefinitionFromAnnotatedTemplate> =
makeScriptDefsFromTemplatesProviders(Extensions.getArea(project).getExtensionPoint(ScriptTemplatesProvider.EP_NAME).extensions.asIterable(),
errorsHandler)
fun makeScriptDefsFromTemplatesProviders(providers: Iterable<ScriptTemplatesProvider>,
errorsHandler: ((ScriptTemplatesProvider, Exception) -> Unit) = { _, ex -> throw ex }
): List<KotlinScriptDefinitionFromAnnotatedTemplate> {
return providers.filter { it.isValid }.flatMap { provider ->
try {
Logger.getInstance("makeScriptDefsFromTemplatesProviders")
.info("[kts] loading script definitions ${provider.templateClassNames} using cp: ${provider.dependenciesClasspath.joinToString(File.pathSeparator)}")
val loader = URLClassLoader(provider.dependenciesClasspath.map { File(it).toURI().toURL() }.toTypedArray(), ScriptTemplatesProvider::class.java.classLoader)
provider.templateClassNames.map {
val cl = loader.loadClass(it)
KotlinScriptDefinitionFromAnnotatedTemplate(cl.kotlin, provider.resolver, provider.filePattern, provider.environment)
}
}
catch (ex: Exception) {
errorsHandler(provider, ex)
emptyList<KotlinScriptDefinitionFromAnnotatedTemplate>()
}
}
}
@@ -0,0 +1,78 @@
/*
* 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 org.jetbrains.kotlin.descriptors.ScriptDescriptor
import org.jetbrains.kotlin.descriptors.annotations.Annotations
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.descriptorUtil.module
import org.jetbrains.kotlin.serialization.deserialization.NotFoundClasses
import org.jetbrains.kotlin.serialization.deserialization.findNonGenericClassAcrossDependencies
import org.jetbrains.kotlin.storage.LockBasedStorageManager
import org.jetbrains.kotlin.types.*
import kotlin.reflect.KClass
import kotlin.reflect.KType
import kotlin.reflect.KTypeProjection
import kotlin.reflect.KVariance
import kotlin.reflect.full.primaryConstructor
fun KotlinScriptDefinition.getScriptParameters(scriptDescriptor: ScriptDescriptor): List<ScriptParameter> =
template.primaryConstructor?.parameters
?.map { ScriptParameter(Name.identifier(it.name!!), getKotlinTypeByKType(scriptDescriptor, it.type)) }
?: emptyList()
fun getKotlinTypeByKClass(scriptDescriptor: ScriptDescriptor, kClass: KClass<out Any>): KotlinType =
getKotlinTypeByFqName(scriptDescriptor,
kClass.qualifiedName ?: throw RuntimeException("Cannot get FQN from $kClass"))
private fun getKotlinTypeByFqName(scriptDescriptor: ScriptDescriptor, fqName: String): KotlinType =
scriptDescriptor.module.findNonGenericClassAcrossDependencies(
ClassId.topLevel(FqName(fqName)),
NotFoundClasses(LockBasedStorageManager.NO_LOCKS, scriptDescriptor.module)
).defaultType
// TODO: support star projections
// TODO: support annotations on types and type parameters
// TODO: support type parameters on types and type projections
private fun getKotlinTypeByKType(scriptDescriptor: ScriptDescriptor, kType: KType): KotlinType {
val classifier = kType.classifier
if (classifier !is KClass<*>)
throw java.lang.UnsupportedOperationException("Only classes are supported as parameters in script template: $classifier")
val type = getKotlinTypeByKClass(scriptDescriptor, classifier)
val typeProjections = kType.arguments.map { getTypeProjection(scriptDescriptor, it) }
val isNullable = kType.isMarkedNullable
return KotlinTypeFactory.simpleType(Annotations.EMPTY, type.constructor, typeProjections, isNullable)
}
private fun getTypeProjection(scriptDescriptor: ScriptDescriptor, kTypeProjection: KTypeProjection): TypeProjection {
val kType = kTypeProjection.type ?: throw java.lang.UnsupportedOperationException("Star projections are not supported")
val type = getKotlinTypeByKType(scriptDescriptor, kType)
val variance = when (kTypeProjection.variance) {
KVariance.IN -> Variance.IN_VARIANCE
KVariance.OUT -> Variance.OUT_VARIANCE
KVariance.INVARIANT -> Variance.INVARIANT
null -> throw java.lang.UnsupportedOperationException("Star projections are not supported")
}
return TypeProjectionImpl(variance, type)
}