Refactoring: extract separate manager to update script configurations of gradle scripts

The main goal is to extract logic that is related to Gradle scripts only to separate place.
Configurations for gradle scripts are updated during import simultaneously, there is no need to manage script configuration update for one file
Note that for Gradle less than 6.0 The DefaultScriptingSupport is used
This commit is contained in:
Natalia Selezneva
2020-04-16 16:18:16 +03:00
parent c6609e9a97
commit 59bdf67aa2
27 changed files with 1126 additions and 667 deletions
@@ -0,0 +1,53 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.scripting.gradle
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.idea.core.script.configuration.utils.ScriptClassRootsCache
import org.jetbrains.kotlin.idea.core.script.configuration.utils.ScriptClassRootsStorage
import org.jetbrains.kotlin.idea.core.script.configuration.utils.ScriptClassRootsStorage.Companion.ScriptClassRoots
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
internal class GradleClassRootsCache(
project: Project,
configuration: Configuration?,
override val fileToConfiguration: (VirtualFile) -> ScriptCompilationConfigurationWrapper?
) : ScriptClassRootsCache(project, extractRoots(configuration)) {
override val rootsCacheKey = ScriptClassRootsStorage.Companion.Key("gradle")
override fun getScriptSdk(file: VirtualFile): Sdk? {
return firstScriptSdk
}
override val firstScriptSdk: Sdk? = configuration?.let { getScriptSdk(configuration.context.javaHome) }
// TODO what should we do if no configuration is loaded yet
override fun contains(file: VirtualFile): Boolean = true
companion object {
fun extractRoots(configuration: Configuration?): ScriptClassRoots {
if (configuration == null) {
return ScriptClassRootsStorage.EMPTY
}
val scriptSdk = getScriptSdk(configuration.context.javaHome)
if (scriptSdk != null && !scriptSdk.isAlreadyIndexed(configuration.context.project)) {
return ScriptClassRootsStorage.Companion.ScriptClassRoots(
configuration.classFilePath,
configuration.sourcePath,
setOf(scriptSdk)
)
}
return ScriptClassRoots(
configuration.classFilePath,
configuration.sourcePath,
getScriptSdk(configuration.context.javaHome)?.let { setOf(it) } ?: setOf()
)
}
}
}
@@ -34,15 +34,14 @@ fun runPartialGradleImport(project: Project) {
)
}
private val kotlinDslNotificationGroupId = "Gradle Kotlin DSL Scripts"
private const val kotlinDslNotificationGroupId = "Gradle Kotlin DSL Scripts"
private var Project.notificationPanel: ScriptConfigurationChangedNotification?
by UserDataProperty<Project, ScriptConfigurationChangedNotification>(Key.create("load.script.configuration.panel"))
fun showNotificationForProjectImport(project: Project, callback: () -> Unit) {
fun showNotificationForProjectImport(project: Project) {
val existingPanel = project.notificationPanel
if (existingPanel != null) {
existingPanel.update(callback)
return
}
@@ -53,7 +52,7 @@ fun showNotificationForProjectImport(project: Project, callback: () -> Unit) {
)
}
val notification = ScriptConfigurationChangedNotification(project, callback)
val notification = ScriptConfigurationChangedNotification(project)
project.notificationPanel = notification
notification.notify(project)
}
@@ -64,7 +63,7 @@ fun hideNotificationForProjectImport(project: Project): Boolean {
return true
}
private class ScriptConfigurationChangedNotification(val project: Project, onClick: () -> Unit) :
private class ScriptConfigurationChangedNotification(val project: Project) :
Notification(
kotlinDslNotificationGroupId,
KotlinIcons.LOAD_SCRIPT_CONFIGURATION,
@@ -76,9 +75,7 @@ private class ScriptConfigurationChangedNotification(val project: Project, onCli
) {
init {
//NotificationGroup.findRegisteredGroup(s) ?: NotificationGroup.balloonGroup(s)
addAction(LoadConfigurationAction(onClick))
addAction(LoadConfigurationAction())
addAction(NotificationAction.createSimple(KotlinIdeaGradleBundle.message("action.label.enable.auto.import")) {
val gradleSettings = ExternalSystemApiUtil.getSettings(project, GradleConstants.SYSTEM_ID)
val projectSettings = gradleSettings.getLinkedProjectsSettings()
@@ -87,22 +84,20 @@ private class ScriptConfigurationChangedNotification(val project: Project, onCli
if (projectSettings != null) {
projectSettings.isUseAutoImport = true
}
runPartialGradleImport(project)
})
}
fun update(onClick: () -> Unit) {
actions.filterIsInstance<LoadConfigurationAction>().forEach { it.onClick = onClick }
}
override fun expire() {
super.expire()
project.notificationPanel = null
}
private class LoadConfigurationAction(var onClick: () -> Unit) : AnAction(KotlinIdeaGradleBundle.message("action.label.import.project")) {
private class LoadConfigurationAction : AnAction(KotlinIdeaGradleBundle.message("action.label.import.project")) {
override fun actionPerformed(e: AnActionEvent) {
onClick()
val project = e.project ?: return
runPartialGradleImport(project)
}
}
}
@@ -8,7 +8,6 @@
package org.jetbrains.kotlin.idea.scripting.gradle
import com.intellij.diff.util.DiffUtil
import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.CommonDataKeys
@@ -46,17 +45,16 @@ fun runPartialGradleImport(project: Project) {
)
}
private var Project.notificationPanel: Boolean?
by UserDataProperty<Project, Boolean>(Key.create("load.script.configuration.panel"))
private var Project.shouldShowLoadConfiguraionsAction: Boolean?
by UserDataProperty<Project, Boolean>(Key.create("load.script.configuration.action"))
fun showNotificationForProjectImport(project: Project, callback: () -> Unit) {
project.notificationPanel = true
(ActionManager.getInstance().getAction("LoadConfigurationAction") as LoadConfigurationAction).onClick = callback
fun showNotificationForProjectImport(project: Project) {
project.shouldShowLoadConfiguraionsAction = true
}
fun hideNotificationForProjectImport(project: Project): Boolean {
project.notificationPanel = false
project.shouldShowLoadConfiguraionsAction = false
return true
}
@@ -65,11 +63,9 @@ class LoadConfigurationAction : AnAction(
KotlinIdeaGradleBundle.message("action.description.load.script.configurations"),
KotlinIcons.LOAD_SCRIPT_CONFIGURATION
) {
var onClick = {}
override fun actionPerformed(e: AnActionEvent) {
onClick()
val project = e.project ?: return
runPartialGradleImport(project)
}
override fun update(e: AnActionEvent) {
@@ -79,24 +75,28 @@ class LoadConfigurationAction : AnAction(
}
private fun ensureValidActionVisibility(e: AnActionEvent) {
if (e.project?.notificationPanel != true) {
if (e.project?.shouldShowLoadConfiguraionsAction != true) {
e.presentation.isVisible = false
return
}
val editor = e.getData(CommonDataKeys.EDITOR) ?: return
when {
DiffUtil.isDiffEditor(editor) -> e.presentation.isVisible = false
!editor.isScriptEditor() -> e.presentation.isVisible = false
if (DiffUtil.isDiffEditor(editor)) {
e.presentation.isVisible = false
return
}
e.presentation.isVisible = editor.isScriptEditor()
}
private fun Editor.isScriptEditor(): Boolean {
val project = project ?: return false
val documentManager = FileDocumentManager.getInstance()
val virtualFile = documentManager.getFile(document)
if (virtualFile is LightVirtualFileBase) return false
if (virtualFile == null || !virtualFile.isValid) return false
val project = project ?: return false
// todo only for gradle script
return virtualFile.findScriptDefinition(project) != null
}
}
@@ -78,12 +78,6 @@ class GradleScriptConfigurationLoader(project: Project) : DefaultScriptConfigura
}
}
val gradleVersion = getGradleVersion(project)
if (gradleVersion != null && kotlinDslScriptsModelImportSupported(gradleVersion)) {
runPartialGradleImport(project)
return true
}
val result = getConfigurationThroughScriptingApi(ktFile, vFile, scriptDefinition)
context.saveNewConfiguration(vFile, result)
return true
@@ -0,0 +1,178 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.scripting.gradle
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.idea.core.script.configuration.ScriptingSupport
import org.jetbrains.kotlin.idea.core.script.configuration.ScriptingSupportHelper
import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptConfigurationUpdater
import org.jetbrains.kotlin.idea.core.script.configuration.utils.ScriptClassRootsCache
import org.jetbrains.kotlin.idea.core.script.configuration.utils.ScriptClassRootsIndexer
import org.jetbrains.kotlin.idea.scripting.gradle.importing.GradleKtsContext
import org.jetbrains.kotlin.idea.scripting.gradle.importing.createGradleKtsContextIfPossible
import org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslScriptModel
import org.jetbrains.kotlin.idea.scripting.gradle.importing.toScriptConfiguration
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.scripting.definitions.findScriptDefinition
import org.jetbrains.kotlin.scripting.resolve.KotlinScriptDefinitionFromAnnotatedTemplate
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
import java.nio.file.Paths
internal data class ConfigurationData(
val templateClasspath: List<String>,
val models: List<KotlinDslScriptModel>
)
internal class Configuration(val context: GradleKtsContext, data: ConfigurationData) {
val scripts = data.models.associateBy { it.file }
val sourcePath = data.models.flatMapTo(mutableSetOf()) { it.sourcePath }
val classFilePath: MutableSet<String> = mutableSetOf()
init {
classFilePath.addAll(data.templateClasspath)
data.models.flatMapTo(classFilePath) { it.classPath }
}
fun scriptModel(file: VirtualFile): KotlinDslScriptModel? {
return scripts[FileUtil.toSystemDependentName(file.path)]
}
}
class GradleScriptingSupport(val project: Project) : ScriptingSupport() {
@Volatile
private var configuration: Configuration? = null
private val rootsIndexer = ScriptClassRootsIndexer(project)
init {
if (isKotlinDslScriptsModelImportSupported(project)) {
ApplicationManager.getApplication().executeOnPooledThread {
val data = KotlinDslScriptModels.read(project)
val gradleKtsContext = createGradleKtsContextIfPossible(project)
if (data != null && gradleKtsContext != null) {
val newConfiguration = Configuration(
gradleKtsContext,
data
)
configuration = newConfiguration
configurationChangedCallback(newConfiguration)
}
}
}
}
override fun recreateRootsCache(): ScriptClassRootsCache {
return GradleClassRootsCache(project, configuration) {
configuration?.let { conf ->
val model = conf.scriptModel(it)
model?.toScriptConfiguration(conf.context)
}
}
}
fun replace(context: GradleKtsContext, models: List<KotlinDslScriptModel>) {
if (models.isEmpty()) return
val anyScript = VfsUtil.findFile(Paths.get(models.first().file), true)!!
val definition = anyScript.findScriptDefinition(context.project) ?: return
val templateClasspath = definition.asLegacyOrNull<KotlinScriptDefinitionFromAnnotatedTemplate>()
?.templateClasspath?.map { it.path } ?: return
val data = ConfigurationData(templateClasspath, models)
KotlinDslScriptModels.write(project, data)
val newConfiguration = Configuration(context, data)
configuration = newConfiguration
configurationChangedCallback(newConfiguration)
}
private fun configurationChangedCallback(newConfiguration: Configuration) {
rootsIndexer.transaction {
if (classpathRoots.hasNotCachedRoots(GradleClassRootsCache.extractRoots(newConfiguration))) {
rootsIndexer.markNewRoot()
}
clearClassRootsCaches(project)
ScriptingSupportHelper.updateHighlighting(project) {
configuration?.scriptModel(it) != null
}
}
hideNotificationForProjectImport(project)
}
fun updateNotification(file: KtFile) {
val vFile = file.originalFile.virtualFile
val scriptModel = configuration?.scriptModel(vFile) ?: return
if (scriptModel.inputs.isUpToDate(project, vFile)) {
hideNotificationForProjectImport(project)
} else {
showNotificationForProjectImport(project)
}
}
override fun isRelated(file: VirtualFile): Boolean {
if (isGradleKotlinScript(file)) {
return isKotlinDslScriptsModelImportSupported(project)
}
return false
}
private fun isKotlinDslScriptsModelImportSupported(project: Project): Boolean {
val gradleVersion = getGradleVersion(project)
return gradleVersion != null && kotlinDslScriptsModelImportSupported(gradleVersion)
}
override fun clearCaches() {
// todo should clear up to date
}
override fun hasCachedConfiguration(file: KtFile): Boolean =
configuration?.scriptModel(file.originalFile.virtualFile) != null
override fun getOrLoadConfiguration(virtualFile: VirtualFile, preloadedKtFile: KtFile?): ScriptCompilationConfigurationWrapper? {
val configuration = configuration
if (configuration == null) {
// todo: show notification "Import gradle project"
return null
} else {
return classpathRoots.getScriptConfiguration(virtualFile)
}
}
override val updater: ScriptConfigurationUpdater
get() = object : ScriptConfigurationUpdater {
override fun ensureUpToDatedConfigurationSuggested(file: KtFile) {
// do nothing for gradle scripts
}
// unused symbol inspection should not initiate loading
override fun ensureConfigurationUpToDate(files: List<KtFile>): Boolean = true
override fun suggestToUpdateConfigurationIfOutOfDate(file: KtFile) {
updateNotification(file)
}
}
companion object {
fun getInstance(project: Project): GradleScriptingSupport {
return SCRIPTING_SUPPORT.getPoint(project).extensionList.firstIsInstance()
}
}
}
@@ -1,32 +0,0 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.scripting.gradle
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationNotificationFactory
class GradleScriptConfigurationNotificationFactory : ScriptConfigurationNotificationFactory {
override fun showNotification(file: VirtualFile, project: Project, onClick: () -> Unit): Boolean {
if (isGradleKotlinScript(file) && isGradleImportCanBeUsed(project)) {
showNotificationForProjectImport(project, onClick)
return true
}
return false
}
override fun hideNotification(file: VirtualFile, project: Project): Boolean {
return hideNotificationForProjectImport(project)
}
private fun isGradleImportCanBeUsed(project: Project): Boolean {
val gradleVersion = getGradleVersion(project)
if (gradleVersion != null && kotlinDslScriptsModelImportSupported(gradleVersion)) {
return true
}
return false
}
}
@@ -5,9 +5,11 @@
package org.jetbrains.kotlin.idea.scripting.gradle.importing
import org.jetbrains.kotlin.idea.scripting.gradle.GradleKotlinScriptConfigurationInputs
data class KotlinDslScriptModel(
val file: String,
val inputsTimeStamp: Long,
val inputs: GradleKotlinScriptConfigurationInputs,
val classPath: List<String>,
val sourcePath: List<String>,
val imports: List<String>,
@@ -7,16 +7,14 @@ package org.jetbrains.kotlin.idea.scripting.gradle.importing
import com.intellij.openapi.components.service
import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.openapi.vfs.VirtualFile
import org.gradle.tooling.model.kotlin.dsl.EditorReportSeverity
import org.gradle.tooling.model.kotlin.dsl.KotlinDslScriptsModel
import org.jetbrains.kotlin.idea.KotlinIdeaGradleBundle
import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager
import org.jetbrains.kotlin.idea.core.script.configuration.cache.CachedConfigurationInputs
import org.jetbrains.kotlin.idea.core.script.configuration.cache.ScriptConfigurationSnapshot
import org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptInputsWatcher
import org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptingSupport
import org.jetbrains.kotlin.idea.scripting.gradle.getGradleScriptInputsStamp
import org.jetbrains.kotlin.scripting.definitions.findScriptDefinition
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
@@ -24,6 +22,8 @@ import org.jetbrains.kotlin.scripting.resolve.VirtualFileScriptSource
import org.jetbrains.kotlin.scripting.resolve.adjustByDefinition
import org.jetbrains.plugins.gradle.service.project.ProjectResolverContext
import org.jetbrains.plugins.gradle.settings.GradleExecutionSettings
import org.jetbrains.plugins.gradle.settings.GradleProjectSettings
import org.jetbrains.plugins.gradle.util.GradleConstants
import java.io.File
import kotlin.script.experimental.api.*
import kotlin.script.experimental.jvm.JvmDependency
@@ -40,8 +40,9 @@ fun processScriptModel(
"Couldn't get KotlinDslScriptsModel for $projectName:\n${model.message}\n${model.stackTrace}"
)
} else {
val models = model.toListOfScriptModels()
resolverCtx.externalSystemTaskId.findProject()?.kotlinDslModels?.addAll(
val project = resolverCtx.externalSystemTaskId.findProject() ?: return
val models = model.toListOfScriptModels(project)
project.kotlinDslModels.addAll(
models
)
if (models.containsErrors()) {
@@ -58,7 +59,7 @@ private fun Collection<KotlinDslScriptModel>.containsErrors(): Boolean {
return any { it.messages.any { it.severity == KotlinDslScriptModel.Severity.ERROR } }
}
private fun KotlinDslScriptsModel.toListOfScriptModels(): List<KotlinDslScriptModel> =
private fun KotlinDslScriptsModel.toListOfScriptModels(project: Project): List<KotlinDslScriptModel> =
scriptModels.map { (file, model) ->
val messages = mutableListOf<KotlinDslScriptModel.Message>()
@@ -91,10 +92,14 @@ private fun KotlinDslScriptsModel.toListOfScriptModels(): List<KotlinDslScriptMo
)
}
// TODO: NPE
val virtualFile = VfsUtil.findFile(file.toPath(), true)!!
// todo(KT-34440): take inputs snapshot before starting import
KotlinDslScriptModel(
file.absolutePath,
System.currentTimeMillis(),
// TODO: NPE
getGradleScriptInputsStamp(project, virtualFile)!!,
model.classPath.map { it.absolutePath },
model.sourcePath.map { it.absolutePath },
model.implicitImports,
@@ -102,69 +107,62 @@ private fun KotlinDslScriptsModel.toListOfScriptModels(): List<KotlinDslScriptMo
)
}
fun createGradleKtsContextIfPossible(project: Project): GradleKtsContext? {
val gradleProjectSettings = ExternalSystemApiUtil.getSettings(project, GradleConstants.SYSTEM_ID)
.getLinkedProjectsSettings()
.filterIsInstance<GradleProjectSettings>().firstOrNull() ?: return null
val gradleExeSettings = ExternalSystemApiUtil.getExecutionSettings<GradleExecutionSettings>(
project,
gradleProjectSettings.externalProjectPath,
GradleConstants.SYSTEM_ID
)
val javaHome = gradleExeSettings.javaHome?.let { File(it)}
return GradleKtsContext(project, javaHome)
}
class GradleKtsContext(val project: Project, val javaHome: File?)
fun KotlinDslScriptModel.toScriptConfiguration(context: GradleKtsContext): ScriptCompilationConfigurationWrapper? {
val scriptFile = File(file)
val virtualFile = VfsUtil.findFile(scriptFile.toPath(), true)!!
val definition = virtualFile.findScriptDefinition(context.project) ?: return null
return ScriptCompilationConfigurationWrapper.FromCompilationConfiguration(
VirtualFileScriptSource(virtualFile),
definition.compilationConfiguration.with {
if (context.javaHome != null) {
jvm.jdkHome(context.javaHome)
}
defaultImports(imports)
dependencies(JvmDependency(classPath.map { File(it) }))
ide.dependenciesSources(JvmDependency(sourcePath.map { File(it) }))
}.adjustByDefinition(definition)
)
}
fun saveScriptModels(
project: Project,
task: ExternalSystemTaskId,
javaHomeStr: String?,
models: List<KotlinDslScriptModel>
) {
val scriptConfigurations = mutableListOf<Pair<VirtualFile, ScriptConfigurationSnapshot>>()
val errorReporter = KotlinGradleDslErrorReporter(project, task)
val javaHome = javaHomeStr?.let { File(it) }
val context = GradleKtsContext(project, javaHome)
models.forEach { model ->
val scriptFile = File(model.file)
val virtualFile = VfsUtil.findFile(scriptFile.toPath(), true)!!
val inputs = getGradleScriptInputsStamp(
project,
virtualFile,
givenTimeStamp = model.inputsTimeStamp
)
val definition = virtualFile.findScriptDefinition(project) ?: return@forEach
val configuration =
definition.compilationConfiguration.with {
if (javaHome != null) {
jvm.jdkHome(javaHome)
}
defaultImports(model.imports)
dependencies(JvmDependency(model.classPath.map {
File(
it
)
}))
ide.dependenciesSources(JvmDependency(model.sourcePath.map {
File(
it
)
}))
}.adjustByDefinition(definition)
scriptConfigurations.add(
Pair(
virtualFile,
ScriptConfigurationSnapshot(
inputs
?: CachedConfigurationInputs.OutOfDate,
listOf(),
ScriptCompilationConfigurationWrapper.FromCompilationConfiguration(
VirtualFileScriptSource(virtualFile),
configuration,
),
),
),
)
errorReporter.reportError(scriptFile, model)
errorReporter.reportError(File(model.file), model)
}
project.service<GradleScriptInputsWatcher>().saveGradleProjectRootsAfterImport(
scriptConfigurations.map { it.first.parent.path }.toSet()
models.map { File(it.file).parent }.toSet()
)
project.service<ScriptConfigurationManager>().saveCompilationConfigurationAfterImport(scriptConfigurations)
GradleScriptingSupport.getInstance(project).replace(context, models)
project.service<GradleScriptInputsWatcher>().clearState()
}
@@ -0,0 +1,143 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.scripting.gradle
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.newvfs.FileAttribute
import org.jetbrains.kotlin.idea.core.util.readString
import org.jetbrains.kotlin.idea.core.util.writeString
import org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslScriptModel
import java.io.DataInputStream
import java.io.DataOutputStream
internal object KotlinDslScriptModels {
private val attribute = FileAttribute("kotlin-dsl-script-models", 2, false)
fun read(project: Project): ConfigurationData? {
return attribute.readAttribute(project.projectFile ?: return null)?.use {
readKotlinDslScriptModels(it)
}
}
fun write(project: Project, configuration: ConfigurationData) {
attribute.writeAttribute(project.projectFile ?: return).use {
writeKotlinDslScriptModels(it, configuration)
}
}
}
internal fun writeKotlinDslScriptModels(output: DataOutputStream, data: ConfigurationData) {
val strings = StringsPool.writer(output)
strings.addStrings(data.templateClasspath)
val scriptModels = data.models
scriptModels.forEach {
strings.addString(it.file)
strings.addStrings(it.classPath)
strings.addStrings(it.sourcePath)
strings.addStrings(it.imports)
}
strings.writeHeader()
strings.writeStringIds(data.templateClasspath)
output.writeList(scriptModels) {
strings.writeStringId(it.file)
output.writeString(it.inputs.sections)
output.writeLong(it.inputs.inputsTS)
strings.writeStringIds(it.classPath)
strings.writeStringIds(it.sourcePath)
strings.writeStringIds(it.imports)
}
}
internal fun readKotlinDslScriptModels(input: DataInputStream): ConfigurationData {
val strings = StringsPool.reader(input)
val templateClasspath = strings.readStrings()
val models = input.readList {
KotlinDslScriptModel(
strings.readString(),
GradleKotlinScriptConfigurationInputs(input.readString(), input.readLong()),
strings.readStrings(),
strings.readStrings(),
strings.readStrings(),
listOf()
)
}
return ConfigurationData(templateClasspath, models)
}
private object StringsPool {
fun writer(output: DataOutputStream) = Writer(output)
class Writer(val output: DataOutputStream) {
var freeze = false
val ids = mutableMapOf<String, Int>()
fun getStringId(string: String) = ids.getOrPut(string) {
check(!freeze)
ids.size
}
fun addString(string: String) {
getStringId(string)
}
fun addStrings(list: Collection<String>) {
list.forEach { addString(it) }
}
fun writeHeader() {
freeze = true
output.writeInt(ids.size)
// sort for optimal performance and compression
ids.keys.sorted().forEachIndexed { index, s ->
ids[s] = index
output.writeString(s)
}
}
fun writeStringId(it: String) {
output.writeInt(getStringId(it))
}
fun writeStringIds(strings: List<String>) {
output.writeInt(strings.size)
strings.forEach {
writeStringId(it)
}
}
}
fun reader(input: DataInputStream): Reader {
val strings = input.readList { input.readString() }
return Reader(input, strings)
}
class Reader(val input: DataInputStream, val strings: List<String>) {
fun getString(id: Int) = strings[id]
fun readString() = getString(input.readInt())
fun readStrings(): List<String> = input.readList { readString() }
}
}
private inline fun <T> DataOutputStream.writeList(list: Collection<T>, write: (T) -> Unit) {
writeInt(list.size)
list.forEach { write(it) }
}
private inline fun <T> DataInputStream.readList(read: () -> T): List<T> {
val n = readInt()
val result = ArrayList<T>(n)
repeat(n) {
result.add(read())
}
return result
}
@@ -0,0 +1,49 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.scripting.gradle
import org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslScriptModel
import org.junit.Test
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.DataInputStream
import java.io.DataOutputStream
import kotlin.test.assertEquals
class KotlinDslScriptModelsTest {
@Test
fun write() {
val buffer = ByteArrayOutputStream()
val conf = ConfigurationData(
listOf("a", "b", "c"),
listOf(
KotlinDslScriptModel(
"a",
GradleKotlinScriptConfigurationInputs("b", 1),
listOf("c", "a", "b"),
listOf("b", "c", "a"),
listOf("i", "c", "b"),
listOf()
),
KotlinDslScriptModel(
"a",
GradleKotlinScriptConfigurationInputs("b", 1),
listOf("c", "a", "b"),
listOf("b", "c", "a"),
listOf("i", "c", "b"),
listOf()
)
)
)
writeKotlinDslScriptModels(DataOutputStream(buffer), conf)
val restored = readKotlinDslScriptModels(DataInputStream(ByteArrayInputStream(buffer.toByteArray())))
assertEquals(conf.toString(), restored.toString())
}
}