diff --git a/plugins/kotlin/base/statistics/kotlin.base.statistics.iml b/plugins/kotlin/base/statistics/kotlin.base.statistics.iml
index 163b106510de..11a7f02fc59e 100644
--- a/plugins/kotlin/base/statistics/kotlin.base.statistics.iml
+++ b/plugins/kotlin/base/statistics/kotlin.base.statistics.iml
@@ -1,5 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $KOTLIN_BUNDLED$/lib/kotlinx-serialization-compiler-plugin.jar
+
+
+
+
+
+
+
@@ -19,5 +41,6 @@
+
\ No newline at end of file
diff --git a/plugins/kotlin/base/statistics/src/org/jetbrains/kotlin/idea/statistics/KotlinJ2KOnboardingFUSCollector.kt b/plugins/kotlin/base/statistics/src/org/jetbrains/kotlin/idea/statistics/KotlinJ2KOnboardingFUSCollector.kt
index 36c62f30588a..86313621cd45 100644
--- a/plugins/kotlin/base/statistics/src/org/jetbrains/kotlin/idea/statistics/KotlinJ2KOnboardingFUSCollector.kt
+++ b/plugins/kotlin/base/statistics/src/org/jetbrains/kotlin/idea/statistics/KotlinJ2KOnboardingFUSCollector.kt
@@ -1,4 +1,4 @@
-// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.idea.statistics
import com.intellij.internal.statistic.eventLog.EventLogGroup
@@ -29,6 +29,7 @@ import org.jetbrains.kotlin.idea.compiler.configuration.KotlinIdePlugin
import org.jetbrains.kotlin.idea.configuration.BuildSystemType
import org.jetbrains.kotlin.idea.configuration.buildSystemType
import org.jetbrains.kotlin.psi.KtFile
+import org.jetbrains.kotlin.utils.addToStdlib.UnsafeCastFunction
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import kotlin.math.absoluteValue
import kotlin.random.Random
@@ -299,6 +300,7 @@ class KotlinOnboardingJ2KSessionService(private val project: Project, private va
/**
* Caches if the project contains a Kotlin file.
*/
+ @OptIn(UnsafeCastFunction::class)
internal suspend fun hasKotlinFiles(): Boolean {
hasKotlinFile?.let { return it }
return readAction {
diff --git a/plugins/kotlin/base/statistics/src/org/jetbrains/kotlin/idea/statistics/KotlinLibraryUsageImportProcessor.kt b/plugins/kotlin/base/statistics/src/org/jetbrains/kotlin/idea/statistics/KotlinLibraryUsageImportProcessor.kt
index 614f6ff57fc0..3851f582c8dd 100644
--- a/plugins/kotlin/base/statistics/src/org/jetbrains/kotlin/idea/statistics/KotlinLibraryUsageImportProcessor.kt
+++ b/plugins/kotlin/base/statistics/src/org/jetbrains/kotlin/idea/statistics/KotlinLibraryUsageImportProcessor.kt
@@ -1,4 +1,4 @@
-// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.idea.statistics
import com.intellij.internal.statistic.libraryUsage.LibraryUsageImportProcessor
@@ -8,9 +8,11 @@ import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtImportDirective
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedElementSelector
+import org.jetbrains.kotlin.utils.addToStdlib.UnsafeCastFunction
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
class KotlinLibraryUsageImportProcessor : LibraryUsageImportProcessor {
+ @OptIn(UnsafeCastFunction::class)
override fun imports(file: PsiFile): List = file.safeAs()?.importDirectives.orEmpty()
override fun isSingleElementImport(import: KtImportDirective): Boolean = !import.isAllUnder
diff --git a/plugins/kotlin/base/statistics/src/org/jetbrains/kotlin/idea/statistics/compilationError/KotlinCompilationErrorProcessedFilesTimeStampRecorder.kt b/plugins/kotlin/base/statistics/src/org/jetbrains/kotlin/idea/statistics/compilationError/KotlinCompilationErrorProcessedFilesTimeStampRecorder.kt
index 06a3b579445a..da422aff83d5 100644
--- a/plugins/kotlin/base/statistics/src/org/jetbrains/kotlin/idea/statistics/compilationError/KotlinCompilationErrorProcessedFilesTimeStampRecorder.kt
+++ b/plugins/kotlin/base/statistics/src/org/jetbrains/kotlin/idea/statistics/compilationError/KotlinCompilationErrorProcessedFilesTimeStampRecorder.kt
@@ -1,19 +1,13 @@
-// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.idea.statistics.compilationError
import com.intellij.openapi.components.*
import com.intellij.openapi.project.Project
-import com.intellij.openapi.util.SimpleModificationTracker
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.io.DigestUtil
import com.intellij.util.io.hashToHexString
-import com.intellij.util.xmlb.annotations.Tag
-import com.intellij.util.xmlb.annotations.XMap
-import org.jetbrains.annotations.ApiStatus
+import kotlinx.serialization.Serializable
import java.util.concurrent.TimeUnit
-import java.util.concurrent.locks.ReentrantReadWriteLock
-import kotlin.concurrent.read
-import kotlin.concurrent.write
@Service(Service.Level.PROJECT)
@State(
@@ -22,75 +16,70 @@ import kotlin.concurrent.write
reportStatistic = false,
reloadable = false,
)
-@ApiStatus.Internal
-class KotlinCompilationErrorProcessedFilesTimeStampRecorder :
- PersistentStateComponentWithModificationTracker {
+internal class KotlinCompilationErrorProcessedFilesTimeStampRecorder :
+ SerializablePersistentStateComponent(EMPTY_STATE) {
- override fun initializeComponent() = lock.write {
- if (dropOutdatedTimestamps()) {
- tracker.incModificationCount()
+ override fun initializeComponent() {
+ val currentTime = System.currentTimeMillis()
+ if (state.timestamps.values.any { currentTime.isHourPassedSince(it) }) {
+ updateState { state ->
+ val timestamps = HashMap(state.timestamps)
+ dropOutdatedTimestamps(currentTime = currentTime, timestamps = timestamps)
+ if (timestamps.isEmpty()) EMPTY_STATE else MyState(timestamps = timestamps)
+ }
}
}
- private val lock = ReentrantReadWriteLock()
- private var state = MyState()
- private val tracker = SimpleModificationTracker()
- override fun getState(): MyState = lock.read { MyState(HashMap(state.timestamps)) }
- override fun getStateModificationCount(): Long = tracker.modificationCount
-
- override fun loadState(state: MyState) = lock.write {
- this.state = state
- }
-
+ @Serializable
class MyState(
- @XMap
- @JvmField
- val timestamps: MutableMap = HashMap()
- ) : BaseState() {
- data class Key(
- // Use md5 instead of the actual file path to make local storage smaller. Users' paths can be huge
- @Tag("filePathMd5")
- val filePathMd5: String,
- @Tag("compilationErrorId")
- val compilationErrorId: String
- )
- }
+ @JvmField val timestamps: Map = emptyMap()
+ )
fun keepOnlyIfHourPassedAndRecordTimestamps(vFile: VirtualFile, compilationErrorIds: List): List {
if (compilationErrorIds.isEmpty()) return emptyList()
val hash = pathMd5Hash(vFile)
- lock.write {
+ val result = mutableListOf()
+ updateState { state ->
+ val timestamps = HashMap(state.timestamps)
val currentTime = System.currentTimeMillis()
- val result = compilationErrorIds.asSequence()
+ result.clear()
+ compilationErrorIds.asSequence()
.distinct() /* `isHourPassed` + `recordTimestamp` don't allow the compilation error to happen for the
second time in this pipeline, so `distinct` is just a small optimization */
- .filter { isHourPassed(hash, currentTime, it) }
- .onEach { recordTimestamp(hash, currentTime, it) }
- .toList()
- if (dropOutdatedTimestamps(currentTime) || result.isNotEmpty()) {
- tracker.incModificationCount()
- }
- return result
+ .filter { isHourPassed(md5Hash = hash, currentTime = currentTime, compilationErrorId = it, timestamps = timestamps) }
+ .onEach { recordTimestamp(md5Hash = hash, currentTime = currentTime, compilationErrorId = it, timestamps = timestamps) }
+ .toCollection(result)
+ dropOutdatedTimestamps(currentTime, timestamps)
+ if (timestamps.isEmpty()) EMPTY_STATE else MyState(timestamps = timestamps)
}
+ return result
}
- private fun isHourPassed(md5Hash: String, currentTime: Long, compilationErrorId: String): Boolean {
- val fileTime = state.timestamps[MyState.Key(md5Hash, compilationErrorId)] ?: return true
- return currentTime.isHourPassedSince(fileTime)
- }
-
- private fun recordTimestamp(md5Hash: String, currentTime: Long, compilationErrorId: String) {
- state.timestamps[MyState.Key(md5Hash, compilationErrorId)] = currentTime
- }
-
- private fun dropOutdatedTimestamps(currentTime: Long = System.currentTimeMillis()): Boolean =
- state.timestamps.values.removeIf { currentTime.isHourPassedSince(it) }
-
companion object {
fun getInstance(project: Project): KotlinCompilationErrorProcessedFilesTimeStampRecorder = project.service()
}
}
+private val EMPTY_STATE = KotlinCompilationErrorProcessedFilesTimeStampRecorder.MyState()
+
+private fun isHourPassed(md5Hash: String, currentTime: Long, compilationErrorId: String, timestamps: Map): Boolean {
+ val fileTime = timestamps[createCompositeKey(md5Hash, compilationErrorId)] ?: return true
+ return currentTime.isHourPassedSince(fileTime)
+}
+
+// Use md5 instead of the actual file path to make local storage smaller.
+// Users' paths can be huge.
+// Key - combination of filePathMd5 + compilationErrorId, to avoid non-primitive key.
+private fun createCompositeKey(md5Hash: String, compilationErrorId: String) = "$md5Hash-$compilationErrorId"
+
+private fun recordTimestamp(md5Hash: String, currentTime: Long, compilationErrorId: String, timestamps: MutableMap) {
+ timestamps[createCompositeKey(md5Hash, compilationErrorId)] = currentTime
+}
+
+private fun dropOutdatedTimestamps(currentTime: Long, timestamps: MutableMap): Boolean {
+ return timestamps.values.removeIf { currentTime.isHourPassedSince(it) }
+}
+
private fun pathMd5Hash(virtualFile: VirtualFile): String = hashToHexString(virtualFile.path, DigestUtil.md5())
private fun Long.isHourPassedSince(lastTime: Long): Boolean = TimeUnit.MILLISECONDS.toHours(this - lastTime) >= 1
\ No newline at end of file