From 3dfbd824e485418cd137adda80800b3d7901c6f9 Mon Sep 17 00:00:00 2001 From: Natalia Selezneva Date: Fri, 27 Dec 2019 15:52:53 +0300 Subject: [PATCH] Implement persistent storage for time stamps of files that affect build script configuration (KT-3556) ^KT-3556 Fixed --- .../gradle/GradleScriptInputsWatcher.kt | 91 ++++++++++++++----- .../gradle/GradleScriptInputsWatcher.kt.as35 | 91 ++++++++++++++----- .../scripting/gradle/GradleScriptListener.kt | 2 +- .../gradle/GradleScriptInputsWatcherTest.kt | 41 +++++++++ 4 files changed, 178 insertions(+), 47 deletions(-) diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptInputsWatcher.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptInputsWatcher.kt index c669388d64e..fba7923c824 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptInputsWatcher.kt +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptInputsWatcher.kt @@ -5,18 +5,23 @@ package org.jetbrains.kotlin.idea.scripting.gradle +import com.intellij.openapi.components.* import com.intellij.openapi.externalSystem.service.project.autoimport.ConfigurationFileCrcFactory 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 com.intellij.util.PathUtil +import org.jetbrains.annotations.TestOnly import org.jetbrains.plugins.gradle.settings.GradleLocalSettings import java.nio.file.Paths -import java.util.* -class GradleScriptInputsWatcher(val project: Project) { - private val comparator = Comparator { f1, f2 -> (f1.timeStamp - f2.timeStamp).toInt() } - - private val storage = TreeSet(comparator) +@State( + name = "KotlinBuildScriptsModificationInfo", + storages = [Storage(StoragePathMacros.CACHE_FILE)] +) +class GradleScriptInputsWatcher(val project: Project) : PersistentStateComponent { + private var storage = Storage() init { initStorage(project) @@ -29,38 +34,78 @@ class GradleScriptInputsWatcher(val project: Project) { if (file != null && !file.isDirectory) { val calculateCrc = ConfigurationFileCrcFactory(file).create() if (calculateCrc != stamp) { - addToStorage(file) + storage.fileChanged(file, file.timeStamp) } } } } - fun lastModifiedFileTimeStamp(file: VirtualFile): Long = lastModifiedRelatedFile(file)?.timeStamp ?: 0 + fun lastModifiedFileTimeStamp(file: VirtualFile): Long = storage.lastModifiedTimeStampExcept(file) fun areRelatedFilesUpToDate(file: VirtualFile, timeStamp: Long): Boolean { - return lastModifiedFileTimeStamp(file) <= timeStamp + return lastModifiedFileTimeStamp(file) < timeStamp } - fun addToStorage(file: VirtualFile) { - if (storage.contains(file)) { - storage.remove(file) + class Storage( + private var lastModifiedTS: Long = 0, + private var lastModifiedFiles: MutableSet = hashSetOf(), + private var previousModifiedTS: Long = 0, + private var previousModifiedFiles: MutableSet = hashSetOf() + ) { + fun lastModifiedTimeStampExcept(file: VirtualFile): Long { + val fileId = getFileId(file) + synchronized(this) { + if (lastModifiedFiles.contains(fileId) && lastModifiedFiles.size == 1) { + return previousModifiedTS + } + return lastModifiedTS + } + + } + + fun fileChanged(file: VirtualFile, ts: Long) { + val fileId = getFileId(file) + synchronized(this) { + when { + ts > lastModifiedTS -> { + previousModifiedFiles = lastModifiedFiles + previousModifiedTS = lastModifiedTS + + lastModifiedFiles = hashSetOf(fileId) + lastModifiedTS = ts + } + ts == lastModifiedTS -> { + lastModifiedFiles.add(fileId) + } + ts == previousModifiedTS -> { + previousModifiedFiles.add(fileId) + } + else -> {} + } + } + } + + private fun getFileId(file: VirtualFile): String { + val canonized = PathUtil.getCanonicalPath(file.path) + return FileUtil.toSystemIndependentName(canonized) } - storage.add(file) } - private fun lastModifiedRelatedFile(file: VirtualFile): VirtualFile? { - if (storage.isEmpty()) return null + override fun getState(): Storage { + return storage + } - val iterator = storage.descendingIterator() - if (!iterator.hasNext()) return null + override fun loadState(state: Storage) { + this.storage = state + } - var lastModifiedFile = iterator.next() - while (lastModifiedFile == file && iterator.hasNext()) { - lastModifiedFile = iterator.next() - } + @TestOnly + fun clearAndRefillState() { + loadState(project.service().state) + } - if (lastModifiedFile == file) return null - - return lastModifiedFile + @TestOnly + fun fileChanged(file: VirtualFile, ts: Long) { + storage.fileChanged(file, ts) } } \ No newline at end of file diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptInputsWatcher.kt.as35 b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptInputsWatcher.kt.as35 index 394a7825e0d..152a3ced77c 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptInputsWatcher.kt.as35 +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptInputsWatcher.kt.as35 @@ -5,18 +5,23 @@ package org.jetbrains.kotlin.idea.scripting.gradle +import com.intellij.openapi.components.* import com.intellij.openapi.externalSystem.service.project.autoimport.ConfigurationFileCrcFactory 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 com.intellij.util.PathUtil +import org.jetbrains.annotations.TestOnly import org.jetbrains.plugins.gradle.settings.GradleLocalSettings import java.nio.file.Paths -import java.util.* -class GradleScriptInputsWatcher(val project: Project) { - private val comparator = Comparator { f1, f2 -> (f1.timeStamp - f2.timeStamp).toInt() } - - private val storage = TreeSet(comparator) +@State( + name = "KotlinBuildScriptsModificationInfo", + storages = [Storage(StoragePathMacros.CACHE_FILE)] +) +class GradleScriptInputsWatcher(val project: Project) : PersistentStateComponent { + private var storage = Storage() init { initStorage(project) @@ -29,38 +34,78 @@ class GradleScriptInputsWatcher(val project: Project) { if (file != null && !file.isDirectory) { val calculateCrc = ConfigurationFileCrcFactory(project, file).create() if (calculateCrc != stamp) { - addToStorage(file) + storage.fileChanged(file, file.timeStamp) } } } } - fun lastModifiedFileTimeStamp(file: VirtualFile): Long = lastModifiedRelatedFile(file)?.timeStamp ?: 0 + fun lastModifiedFileTimeStamp(file: VirtualFile): Long = storage.lastModifiedTimeStampExcept(file) fun areRelatedFilesUpToDate(file: VirtualFile, timeStamp: Long): Boolean { - return lastModifiedFileTimeStamp(file) <= timeStamp + return lastModifiedFileTimeStamp(file) < timeStamp } - fun addToStorage(file: VirtualFile) { - if (storage.contains(file)) { - storage.remove(file) + class Storage( + private var lastModifiedTS: Long = 0, + private var lastModifiedFiles: MutableSet = hashSetOf(), + private var previousModifiedTS: Long = 0, + private var previousModifiedFiles: MutableSet = hashSetOf() + ) { + fun lastModifiedTimeStampExcept(file: VirtualFile): Long { + val fileId = getFileId(file) + synchronized(this) { + if (lastModifiedFiles.contains(fileId) && lastModifiedFiles.size == 1) { + return previousModifiedTS + } + return lastModifiedTS + } + + } + + fun fileChanged(file: VirtualFile, ts: Long) { + val fileId = getFileId(file) + synchronized(this) { + when { + ts > lastModifiedTS -> { + previousModifiedFiles = lastModifiedFiles + previousModifiedTS = lastModifiedTS + + lastModifiedFiles = hashSetOf(fileId) + lastModifiedTS = ts + } + ts == lastModifiedTS -> { + lastModifiedFiles.add(fileId) + } + ts == previousModifiedTS -> { + previousModifiedFiles.add(fileId) + } + else -> {} + } + } + } + + private fun getFileId(file: VirtualFile): String { + val canonized = PathUtil.getCanonicalPath(file.path) + return FileUtil.toSystemIndependentName(canonized) } - storage.add(file) } - private fun lastModifiedRelatedFile(file: VirtualFile): VirtualFile? { - if (storage.isEmpty()) return null + override fun getState(): Storage { + return storage + } - val iterator = storage.descendingIterator() - if (!iterator.hasNext()) return null + override fun loadState(state: Storage) { + this.storage = state + } - var lastModifiedFile = iterator.next() - while (lastModifiedFile == file && iterator.hasNext()) { - lastModifiedFile = iterator.next() - } + @TestOnly + fun clearAndRefillState() { + loadState(project.service().state) + } - if (lastModifiedFile == file) return null - - return lastModifiedFile + @TestOnly + fun fileChanged(file: VirtualFile, ts: Long) { + storage.fileChanged(file, ts) } } \ No newline at end of file diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptListener.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptListener.kt index c1daa5ff71e..0a8b0d2fa58 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptListener.kt +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptListener.kt @@ -36,7 +36,7 @@ open class GradleScriptListener(project: Project) : ScriptChangeListener(project // *.gradle.kts file was changed updater.ensureUpToDatedConfigurationSuggested(file) } - project.service().addToStorage(vFile) + project.service().fileChanged(vFile, vFile.timeStamp) } override fun isApplicable(vFile: VirtualFile): Boolean { diff --git a/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptInputsWatcherTest.kt b/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptInputsWatcherTest.kt index 99fd10ab715..d6f16ec1f7b 100644 --- a/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptInputsWatcherTest.kt +++ b/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptInputsWatcherTest.kt @@ -6,6 +6,7 @@ package org.jetbrains.kotlin.idea.scripting.gradle import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.components.service import com.intellij.psi.PsiFile import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptChangeListener @@ -182,6 +183,46 @@ class GradleScriptInputsWatcherTest : AbstractScriptConfigurationLoadingTest() { assertConfigurationUpdateWasDone(testFiles.buildKts) } + fun testConfigurationUpdateAfterProjectClosing() { + assertAndLoadInitialConfiguration(testFiles.buildKts) + assertAndLoadInitialConfiguration(testFiles.settings) + + changeSettingsKtsOutsideSections() + + project.service().clearAndRefillState() + + assertConfigurationUpToDate(testFiles.settings) + assertConfigurationUpdateWasDone(testFiles.buildKts) + } + + fun testConfigurationUpdateAfterProjectClosing2() { + assertAndLoadInitialConfiguration(testFiles.buildKts) + assertAndLoadInitialConfiguration(testFiles.settings) + + changeSettingsKtsOutsideSections() + + val ts = System.currentTimeMillis() + project.service().fileChanged(testFiles.buildKts.virtualFile, ts) + project.service().fileChanged(testFiles.settings.virtualFile, ts) + + assertConfigurationUpdateWasDone(testFiles.settings) + assertConfigurationUpdateWasDone(testFiles.buildKts) + } + + fun testConfigurationUpdateAfterProjectClosing3() { + assertAndLoadInitialConfiguration(testFiles.buildKts) + assertAndLoadInitialConfiguration(testFiles.settings) + + val ts = System.currentTimeMillis() + project.service().fileChanged(testFiles.buildKts.virtualFile, ts) + project.service().fileChanged(testFiles.settings.virtualFile, ts) + + changePropertiesFile() + + assertConfigurationUpdateWasDone(testFiles.settings) + assertConfigurationUpdateWasDone(testFiles.buildKts) + } + fun testLoadedConfigurationWhenExternalFileChanged() { assertAndLoadInitialConfiguration(testFiles.buildKts)