Script IDE API: remove <TF: Any>

This commit is contained in:
Pavel V. Talanov
2017-06-19 21:23:27 +03:00
parent 581176fa90
commit b6a7d831bf
12 changed files with 63 additions and 143 deletions
@@ -180,7 +180,7 @@ class K2JVMCompiler : CLICompiler<K2JVMCompilerArguments>() {
val scriptDefinitionProvider = KotlinScriptDefinitionProvider.getInstance(environment.project)!!
val scriptFile = File(sourcePath)
if (scriptFile.isDirectory || !scriptDefinitionProvider.isScript(scriptFile)) {
if (scriptFile.isDirectory || !scriptDefinitionProvider.isScript(scriptFile.name)) {
val extensionHint =
if (configuration.get(JVMConfigurationKeys.SCRIPT_DEFINITIONS) == listOf(StandardScriptDefinition)) " (.kts)"
else ""
@@ -185,7 +185,7 @@ class KotlinCoreEnvironment private constructor(
scriptDefinitionProvider.setScriptDefinitions(
configuration.getList(JVMConfigurationKeys.SCRIPT_DEFINITIONS))
KotlinScriptExternalImportsProvider.getInstance(project)?.let { importsProvider ->
KotlinScriptExternalImportsProvider.getInstance(project).let { importsProvider ->
configuration.addJvmClasspathRoots(
sourceFiles.mapNotNull(importsProvider::getScriptDependencies)
.flatMap { it.classpath }
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.script
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.name.NameUtils
import org.jetbrains.kotlin.psi.KtScript
@@ -109,13 +110,13 @@ open class KotlinScriptDefinitionFromAnnotatedTemplate(
override val name = template.simpleName!!
override fun <TF: Any> isScript(file: TF): Boolean =
scriptFilePattern.let { Regex(it).matches(getFileName(file)) }
override fun isScript(fileName: String): Boolean =
scriptFilePattern.let { Regex(it).matches(fileName) }
// 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?) = error("Should not be called")
override fun getDependenciesFor(file: VirtualFile, project: Project, previousDependencies: KotlinScriptExternalDependencies?) = error("Should not be called")
override fun toString(): String = "KotlinScriptDefinitionFromAnnotatedTemplate - ${template.simpleName}"
@@ -17,7 +17,6 @@
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
@@ -29,10 +28,10 @@ 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)
fun getScriptContents(scriptDefinition: KotlinScriptDefinition, file: VirtualFile)
= BasicScriptContents(file, getAnnotations = { loadAnnotations(scriptDefinition, file) })
private fun <TF : Any> loadAnnotations(scriptDefinition: KotlinScriptDefinition, file: TF): List<Annotation> {
private fun loadAnnotations(scriptDefinition: KotlinScriptDefinition, file: VirtualFile): List<Annotation> {
val classLoader = scriptDefinition.template.java.classLoader
// TODO_R: report error on failure to load annotation class
return getAnnotationEntries(file, project)
@@ -44,36 +43,22 @@ abstract class KotlinScriptExternalImportsProviderBase(private val project: Proj
}
}
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> {
private fun getAnnotationEntries(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)
return (psiFile as? KtFile)?.annotationEntries
?: throw IllegalArgumentException("Unable to extract kotlin annotations from ${file.name} (${file.fileType})")
}
class BasicScriptContents<out TF: Any>(myFile: TF, getAnnotations: () -> Iterable<Annotation>) : ScriptContents {
override val file: File? by lazy { getFile(myFile) }
class BasicScriptContents(virtualFile: VirtualFile, getAnnotations: () -> Iterable<Annotation>) : ScriptContents {
override val file: File = File(virtualFile.path)
override val annotations: Iterable<Annotation> by lazy { getAnnotations() }
override val text: CharSequence? by lazy { getFileContents(myFile) }
override val text: CharSequence? by lazy { virtualFile.inputStream.reader(charset = virtualFile.charset).readText() }
}
fun <TF: Any> resolveDependencies(
fun resolveDependencies(
scriptDef: KotlinScriptDefinition,
file: TF,
file: VirtualFile,
oldDependencies: KotlinScriptExternalDependencies? = null
): KotlinScriptExternalDependencies? {
val scriptContents = getScriptContents(scriptDef, file)
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.script
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import java.io.File
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.read
@@ -32,12 +33,12 @@ class KotlinScriptExternalImportsProviderImpl(
private val cacheLock = ReentrantReadWriteLock()
private val cache = hashMapOf<String, KotlinScriptExternalDependencies?>()
override fun <TF : Any> getScriptDependencies(file: TF): KotlinScriptExternalDependencies? = cacheLock.read {
override fun getScriptDependencies(file: VirtualFile): KotlinScriptExternalDependencies? = cacheLock.read {
calculateExternalDependencies(file)
}
private fun <TF : Any> calculateExternalDependencies(file: TF): KotlinScriptExternalDependencies? {
val path = getFilePath(file)
private fun calculateExternalDependencies(file: VirtualFile): KotlinScriptExternalDependencies? {
val path = file.path
val cached = cache[path]
return if (cached != null) cached
else {
@@ -43,7 +43,7 @@ public class KotlinParser implements PsiParser {
@NotNull
public ASTNode parse(IElementType iElementType, PsiBuilder psiBuilder, PsiFile psiFile) {
KotlinParsing ktParsing = KotlinParsing.createForTopLevel(new SemanticWhitespaceAwarePsiBuilderImpl(psiBuilder));
if (scriptDefinitionProvider != null && scriptDefinitionProvider.isScript(psiFile)
if (scriptDefinitionProvider != null && scriptDefinitionProvider.isScript(psiFile.getName())
|| psiFile.getName().endsWith(KotlinParserDefinition.STD_SCRIPT_EXT)) {
ktParsing.parseScript();
}
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.script
import com.intellij.openapi.fileTypes.LanguageFileType
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.name.NameUtils
@@ -38,14 +39,14 @@ open class KotlinScriptDefinition(val template: KClass<out Any>) {
open val annotationsForSamWithReceivers: List<String>
get() = emptyList()
open fun <TF : Any> isScript(file: TF): Boolean =
getFileName(file).endsWith(KotlinParserDefinition.STD_SCRIPT_EXT)
open fun isScript(fileName: String): Boolean =
fileName.endsWith(KotlinParserDefinition.STD_SCRIPT_EXT)
open fun getScriptName(script: KtScript): Name =
NameUtils.getScriptNameForFile(script.containingKtFile.name)
@Deprecated("Use dependencyResolver instead", level = DeprecationLevel.ERROR)
open fun <TF : Any> getDependenciesFor(file: TF, project: Project, previousDependencies: KotlinScriptExternalDependencies?): KotlinScriptExternalDependencies? = null
open fun getDependenciesFor(file: VirtualFile, project: Project, previousDependencies: KotlinScriptExternalDependencies?): KotlinScriptExternalDependencies? = null
open val dependencyResolver: ScriptDependenciesResolver = EmptyDependencyResolver
@@ -43,11 +43,15 @@ class KotlinScriptDefinitionProvider {
return changed
}
fun<TF: Any> findScriptDefinition(file: TF): KotlinScriptDefinition? = definitionsLock.read {
definitions.firstOrNull { it.isScript(file) }
fun findScriptDefinition(file: VirtualFile): KotlinScriptDefinition? = findScriptDefinition(file.name)
fun findScriptDefinition(fileName: String): KotlinScriptDefinition? = definitionsLock.read {
definitions.firstOrNull { it.isScript(fileName) }
}
fun<TF: Any> isScript(file: TF): Boolean = findScriptDefinition(file) != null
fun isScript(fileName: String): Boolean = definitionsLock.read {
definitions.any { it.isScript(fileName) }
}
fun addScriptDefinition(scriptDefinition: KotlinScriptDefinition) {
definitionsLock.write {
@@ -76,4 +80,4 @@ fun getScriptDefinition(file: VirtualFile, project: Project): KotlinScriptDefini
KotlinScriptDefinitionProvider.getInstance(project)?.findScriptDefinition(file)
fun getScriptDefinition(psiFile: PsiFile): KotlinScriptDefinition? =
KotlinScriptDefinitionProvider.getInstance(psiFile.project)?.findScriptDefinition(psiFile)
KotlinScriptDefinitionProvider.getInstance(psiFile.project)?.findScriptDefinition(psiFile.name)
@@ -19,10 +19,12 @@ package org.jetbrains.kotlin.script
import com.intellij.openapi.components.ServiceManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiFile
import kotlin.script.dependencies.KotlinScriptExternalDependencies
interface KotlinScriptExternalImportsProvider {
fun <TF: Any> getScriptDependencies(file: TF): KotlinScriptExternalDependencies?
fun getScriptDependencies(file: VirtualFile): KotlinScriptExternalDependencies?
fun getScriptDependencies(file: PsiFile) = getScriptDependencies(file.virtualFile)
companion object {
fun getInstance(project: Project): KotlinScriptExternalImportsProvider =
@@ -1,65 +0,0 @@
/*
* 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.vfs.VirtualFile
import com.intellij.psi.PsiFile
import java.io.File
import java.io.InputStream
fun <TF: Any> getFileName(file: TF): String = when (file) {
is PsiFile -> file.originalFile.name
is VirtualFile -> file.name
is File -> file.name
else -> throw IllegalArgumentException("Unsupported file type $file")
}
fun <TF: Any> getFilePath(file: TF): String = when (file) {
is PsiFile -> file.originalFile.run { virtualFile?.path ?: name } // TODO: replace name with path of PSI elements
is VirtualFile -> file.path
is File -> file.canonicalPath
else -> throw IllegalArgumentException("Unsupported file type $file")
}
fun <TF: Any> isValidFile(file: TF): Boolean = when (file) {
is PsiFile -> file.isValid
is VirtualFile -> file.isValid
is File -> file.exists() && file.isFile
else -> throw IllegalArgumentException("Unsupported file type $file")
}
fun <TF: Any> getFile(file: TF): File? = when (file) {
is PsiFile -> file.originalFile.run { File(virtualFile?.path) }
is VirtualFile -> File(file.path)
is File -> file
else -> throw IllegalArgumentException("Unsupported file type $file")
}
fun <TF: Any> getFileContents(file: TF): CharSequence = when (file) {
is PsiFile -> file.viewProvider.contents
is VirtualFile -> file.inputStream.reader(charset = file.charset).readText()
is File -> file.readText()
else -> throw IllegalArgumentException("Unsupported file type $file")
}
fun <TF: Any> getFileContentsStream(file: TF): InputStream = when (file) {
is PsiFile -> file.viewProvider.contents.toString().byteInputStream()
is VirtualFile -> file.inputStream
is File -> file.inputStream()
else -> throw IllegalArgumentException("Unsupported file type $file")
}
@@ -54,7 +54,7 @@ import kotlin.script.dependencies.KotlinScriptExternalDependencies
class IdeScriptExternalImportsProvider(
private val scriptConfigurationManager: KotlinScriptConfigurationManager
) : KotlinScriptExternalImportsProvider {
override fun <TF : Any> getScriptDependencies(file: TF): KotlinScriptExternalDependencies? {
override fun getScriptDependencies(file: VirtualFile): KotlinScriptExternalDependencies? {
return scriptConfigurationManager.getScriptDependencies(file)
}
}
@@ -154,8 +154,8 @@ class KotlinScriptConfigurationManager(
private val cache = hashMapOf<String, DataAndRequest?>()
override fun <TF : Any> getScriptDependencies(file: TF): KotlinScriptExternalDependencies = cacheLock.read {
val path = getFilePath(file)
override fun getScriptDependencies(file: VirtualFile): KotlinScriptExternalDependencies = cacheLock.read {
val path = file.path
val cached = cache[path]
cached?.dependencies?.let { return it }
@@ -166,8 +166,8 @@ class KotlinScriptConfigurationManager(
return cache[path]?.dependencies ?: NoDependencies
}
private fun <TF : Any> tryLoadingFromDisk(cached: DataAndRequest?, file: TF, path: String) {
if (cached != null || file !is VirtualFile) return
private fun tryLoadingFromDisk(cached: DataAndRequest?, file: VirtualFile, path: String) {
if (cached != null) return
ScriptDependenciesFileAttribute.read(file)?.let { deserialized ->
save(path, deserialized, file, persist = false)
@@ -176,7 +176,7 @@ class KotlinScriptConfigurationManager(
}
}
private fun <TF: Any> updateExternalImportsCache(files: Iterable<TF>) = cacheLock.write {
private fun updateExternalImportsCache(files: Iterable<VirtualFile>) = cacheLock.write {
files.mapNotNull { file ->
scriptDefinitionProvider.findScriptDefinition(file)?.let {
updateForFile(file, it)
@@ -184,9 +184,9 @@ class KotlinScriptConfigurationManager(
}
}.contains(true)
private fun <TF : Any> updateForFile(file: TF, scriptDef: KotlinScriptDefinition): Boolean {
if (!isValidFile(file)) {
return cache.remove(getFilePath(file)) != null
private fun updateForFile(file: VirtualFile, scriptDef: KotlinScriptDefinition): Boolean {
if (!file.isValid) {
return cache.remove(file.path) != null
}
// TODO: support apis that allow for async updates for any template
@@ -196,8 +196,8 @@ class KotlinScriptConfigurationManager(
return updateSync(file, scriptDef)
}
private fun <TF : Any> updateAsync(file: TF, scriptDefinition: KotlinScriptDefinitionFromAnnotatedTemplate): Boolean {
val path = getFilePath(file)
private fun updateAsync(file: VirtualFile, scriptDefinition: KotlinScriptDefinitionFromAnnotatedTemplate): Boolean {
val path = file.path
val oldDataAndRequest = cache[path]
if (!shouldSendNewRequest(file, oldDataAndRequest)) {
@@ -210,16 +210,16 @@ class KotlinScriptConfigurationManager(
cache[path] = DataAndRequest(
oldDataAndRequest?.dependencies,
fileModificationStamp(file),
file.modificationStamp,
TimeStampedRequest(newFuture, currentTimeStamp)
)
return false // not changed immediately
}
private fun <TF : Any> sendRequest(
private fun sendRequest(
path: String,
scriptDefinition: KotlinScriptDefinitionFromAnnotatedTemplate,
file: TF,
file: VirtualFile,
oldDataAndRequest: DataAndRequest?
): Pair<TimeStamp, CompletableFuture<Unit>> {
val currentTimeStamp = TimeStamps.next()
@@ -229,7 +229,7 @@ class KotlinScriptConfigurationManager(
cacheLock.read {
val lastTimeStamp = cache[path]?.requestInProgress?.timeStamp
if (lastTimeStamp == currentTimeStamp) {
if (cacheSync(newDependencies, oldDataAndRequest?.dependencies, path, file as? VirtualFile)) {
if (cacheSync(newDependencies, oldDataAndRequest?.dependencies, path, file)) {
invalidateLocalCaches()
notifyRootsChanged()
}
@@ -239,31 +239,29 @@ class KotlinScriptConfigurationManager(
return Pair(currentTimeStamp, newFuture)
}
private fun <TF : Any> shouldSendNewRequest(file: TF, oldDataAndRequest: DataAndRequest?): Boolean {
val currentStamp = fileModificationStamp(file)
private fun shouldSendNewRequest(file: VirtualFile, oldDataAndRequest: DataAndRequest?): Boolean {
val currentStamp = file.modificationStamp
val previousStamp = oldDataAndRequest?.modificationStamp
if (currentStamp == null || currentStamp != previousStamp) {
if (currentStamp != previousStamp) {
return true
}
return oldDataAndRequest.requestInProgress == null
}
private fun <TF : Any> fileModificationStamp(file: TF) = (file as? VirtualFile)?.modificationStamp
private fun <TF : Any> updateSync(file: TF, scriptDef: KotlinScriptDefinition): Boolean {
val path = getFilePath(file)
private fun updateSync(file: VirtualFile, scriptDef: KotlinScriptDefinition): Boolean {
val path = file.path
val oldDeps = cache[path]?.dependencies
val newDeps = resolveDependencies(scriptDef, file, oldDeps) ?: EmptyDependencies
return cacheSync(newDeps, oldDeps, path, file as? VirtualFile)
return cacheSync(newDeps, oldDeps, path, file)
}
private fun cacheSync(
new: KotlinScriptExternalDependencies,
old: KotlinScriptExternalDependencies?,
path: String,
file: VirtualFile?
file: VirtualFile
): Boolean {
return when {
old == null || !(new.match(old)) -> {
@@ -279,11 +277,11 @@ class KotlinScriptConfigurationManager(
}
}
private fun save(path: String, new: KotlinScriptExternalDependencies?, virtualFile: VirtualFile?, persist: Boolean) {
private fun save(path: String, new: KotlinScriptExternalDependencies?, virtualFile: VirtualFile, persist: Boolean) {
cacheLock.write {
cache.put(path, DataAndRequest(new, virtualFile?.modificationStamp))
cache.put(path, DataAndRequest(new, virtualFile.modificationStamp))
}
if (persist && new != null && virtualFile != null) {
if (persist && new != null) {
ScriptDependenciesFileAttribute.write(virtualFile, new)
}
}
@@ -42,8 +42,6 @@ import com.intellij.openapi.module.Module
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.vfs.CharsetToolkit
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiFileFactory
import com.intellij.psi.PsiManager
import com.intellij.psi.impl.PsiFileFactoryImpl
@@ -126,13 +124,8 @@ class KotlinConsoleRunner(
private val consoleScriptDefinition = object : KotlinScriptDefinition(Any::class) {
override val name = "Kotlin REPL"
override fun <TF: Any> isScript(file: TF): Boolean {
val vf = when (file) {
is PsiFile -> file.originalFile.virtualFile
is VirtualFile -> file
else -> return false
}
return vf == consoleView.virtualFile
override fun isScript(fileName: String): Boolean {
return fileName == consoleView.virtualFile.name
}
override fun getScriptName(script: KtScript) = Name.identifier("REPL")
}