Skip to content

Commit

Permalink
Adding coroutines to analysis process
Browse files Browse the repository at this point in the history
  • Loading branch information
sellophane committed Apr 8, 2019
1 parent 5150404 commit 3f93585
Show file tree
Hide file tree
Showing 14 changed files with 322 additions and 306 deletions.
2 changes: 2 additions & 0 deletions kotlin-bundled-compiler/.classpath
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry exported="true" kind="lib" path="lib/kotlinx-coroutines-core.jar"/>
<classpathentry exported="true" kind="lib" path="lib/kotlinx-coroutines-jdk8.jar"/>
<classpathentry exported="true" kind="lib" path="lib/ide-dependencies.jar"/>
<classpathentry exported="true" kind="lib" path="lib/kotlin-compiler.jar" sourcepath="lib/kotlin-compiler-sources.jar"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
Expand Down
8 changes: 7 additions & 1 deletion kotlin-bundled-compiler/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ Bundle-ClassPath: .,
lib/kotlin-reflect.jar,
../kotlin-eclipse-ui-test/lib/gson-2.3.1.jar,
lib/kotlin-formatter.jar,
lib/ide-dependencies.jar
lib/ide-dependencies.jar,
lib/kotlinx-coroutines-core.jar,
lib/kotlinx-coroutines-jdk8.jar
Export-Package:
com.intellij,
com.intellij.codeInsight,
Expand Down Expand Up @@ -177,6 +179,9 @@ Export-Package:
kotlin.collections,
kotlin.comparisons,
kotlin.concurrent,
kotlin.coroutines,
kotlin.coroutines.intrinsics,
kotlin.coroutines.jvm.internal,
kotlin.internal;x-internal:=true,
kotlin.io,
kotlin.jvm,
Expand All @@ -194,6 +199,7 @@ Export-Package:
kotlin.script.templates.standard,
kotlin.sequences,
kotlin.text,
kotlinx.coroutines,
org.jetbrains.annotations,
org.jetbrains.kotlin,
org.jetbrains.kotlin.analyzer,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,44 +16,51 @@
*******************************************************************************/
package org.jetbrains.kotlin.core.model

import org.eclipse.jdt.core.IJavaProject
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.async
import kotlinx.coroutines.runBlocking
import org.jetbrains.kotlin.core.resolve.AnalysisResultWithProvider
import org.jetbrains.kotlin.core.resolve.EclipseAnalyzerFacadeForJVM
import org.jetbrains.kotlin.core.resolve.KotlinCoroutinesScope
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.core.builder.KotlinPsiManager

data class FileAnalysisResults(val file: KtFile, val analysisResult: AnalysisResultWithProvider)
data class FileAnalysisResults(val file: KtFile, val analysisResult: Deferred<AnalysisResultWithProvider>)

public object KotlinAnalysisFileCache {
private @Volatile var lastAnalysedFileCache: FileAnalysisResults? = null
object KotlinAnalysisFileCache {

@Synchronized fun getAnalysisResult(file: KtFile): AnalysisResultWithProvider {
return getImmediatlyFromCache(file) ?: run {
@Volatile
private var lastAnalysedFileCache: FileAnalysisResults? = null

fun getAnalysisResult(file: KtFile): AnalysisResultWithProvider {
return runBlocking {
getImmediatelyFromCache(file)?.await()
} ?: runBlocking {
val environment = getEnvironment(file.project)!!
val analysisResult = resolve(file, environment)

lastAnalysedFileCache = FileAnalysisResults(file, analysisResult)
lastAnalysedFileCache!!.analysisResult
val analysisResult = KotlinCoroutinesScope.async {
resolve(file, environment)
}
saveLastResult(file, analysisResult).analysisResult.await()
}
}

@Synchronized
private fun saveLastResult(file: KtFile, result: Deferred<AnalysisResultWithProvider>): FileAnalysisResults =
FileAnalysisResults(file, result).also {
lastAnalysedFileCache = it
}

fun resetCache() {
lastAnalysedFileCache = null
}

private fun resolve(file: KtFile, environment: KotlinCommonEnvironment): AnalysisResultWithProvider {
return when (environment) {
private fun resolve(file: KtFile, environment: KotlinCommonEnvironment): AnalysisResultWithProvider =
when (environment) {
is KotlinScriptEnvironment -> EclipseAnalyzerFacadeForJVM.analyzeScript(environment, file)
is KotlinEnvironment -> EclipseAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(environment, listOf(file))
else -> throw IllegalArgumentException("Could not analyze file with environment: $environment")
}
}

@Synchronized
private fun getImmediatlyFromCache(file: KtFile): AnalysisResultWithProvider? {
return if (lastAnalysedFileCache != null && lastAnalysedFileCache!!.file == file)
lastAnalysedFileCache!!.analysisResult
else
null
}
private fun getImmediatelyFromCache (ktFile: KtFile) : Deferred<AnalysisResultWithProvider>? =
lastAnalysedFileCache?.takeIf { it.file == ktFile }?.analysisResult
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
*******************************************************************************/
package org.jetbrains.kotlin.core.model

import kotlinx.coroutines.Deferred
import kotlinx.coroutines.async
import kotlinx.coroutines.runBlocking
import org.eclipse.core.resources.IFile
import org.eclipse.core.resources.IProject
import org.eclipse.core.resources.IResourceChangeEvent
Expand All @@ -24,52 +27,56 @@ import org.eclipse.jdt.core.IJavaProject
import org.eclipse.jdt.core.JavaCore
import org.jetbrains.kotlin.analyzer.AnalysisResult
import org.jetbrains.kotlin.core.builder.KotlinPsiManager
import org.jetbrains.kotlin.core.resolve.EclipseAnalyzerFacadeForJVM
import org.jetbrains.kotlin.core.utils.ProjectUtils
import java.util.concurrent.ConcurrentHashMap
import org.jetbrains.kotlin.core.resolve.AnalysisResultWithProvider
import org.jetbrains.kotlin.core.resolve.KotlinAnalyzer
import org.jetbrains.kotlin.core.resolve.KotlinCoroutinesScope
import java.util.concurrent.*

public object KotlinAnalysisProjectCache : IResourceChangeListener {
private val cachedAnalysisResults = ConcurrentHashMap<IProject, AnalysisResult>()
object KotlinAnalysisProjectCache : IResourceChangeListener {
private val cachedAnalysisResults = ConcurrentHashMap<IProject, Deferred<AnalysisResultWithProvider>>()

public fun resetCache(project: IProject) {
fun resetCache(project: IProject) {
synchronized(project) {
cachedAnalysisResults.remove(project)
cachedAnalysisResults.remove(project)?.cancel()
}
}

public fun resetAllCaches() {
fun resetAllCaches() {
cachedAnalysisResults.keys.toList().forEach {
resetCache(it)
}
}

public fun getAnalysisResult(javaProject: IJavaProject): AnalysisResult {
val project = javaProject.getProject()
return synchronized(project) {
val analysisResult = cachedAnalysisResults.get(project) ?: run {
EclipseAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(
KotlinEnvironment.getEnvironment(project),
ProjectUtils.getSourceFiles(javaProject.getProject())).analysisResult
fun cancelablePostAnalysis(javaProject: IJavaProject, post: (AnalysisResult) -> Unit) {
try {
KotlinCoroutinesScope.async {
resetCache(javaProject.project)
post(getAnalysisResult(javaProject).analysisResult)
}

cachedAnalysisResults.putIfAbsent(project, analysisResult) ?: analysisResult
} catch (e: CancellationException) {
println("Job cancelled!")
}
}

public @Synchronized fun getAnalysisResultIfCached(project: IProject): AnalysisResult? {
return cachedAnalysisResults.get(project)
fun getAnalysisResult(javaProject: IJavaProject) : AnalysisResultWithProvider {
return runBlocking {
val project = javaProject.project
synchronized(this@KotlinAnalysisProjectCache) {
cachedAnalysisResults.getOrPut(project) { KotlinAnalyzer.analyzeProjectAsync(project) }
}.await()
}
}

override fun resourceChanged(event: IResourceChangeEvent) {
when (event.getType()) {
when (event.type) {
IResourceChangeEvent.PRE_DELETE,
IResourceChangeEvent.PRE_CLOSE,
IResourceChangeEvent.PRE_BUILD -> event.getDelta()?.accept { delta ->
val resource = delta.getResource()
IResourceChangeEvent.PRE_BUILD -> event.delta?.accept { delta ->
val resource = delta.resource
if (resource is IFile) {
val javaProject = JavaCore.create(resource.getProject())
if (KotlinPsiManager.isKotlinSourceFile(resource, javaProject)) {
cachedAnalysisResults.remove(resource.getProject())
resetCache(resource.getProject())
}

return@accept false
Expand All @@ -79,4 +86,4 @@ public object KotlinAnalysisProjectCache : IResourceChangeListener {
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ object EclipseAnalyzerFacadeForJVM {
}

val allFiles = LinkedHashSet<KtFile>(filesSet)
val addedFiles = filesSet.map { getPath(it) }.filterNotNull().toSet()
val addedFiles = filesSet.mapNotNull { getPath(it) }.toSet()
ProjectUtils.getSourceFilesWithDependencies(environment.javaProject).filterNotTo(allFiles) {
getPath(it) in addedFiles
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,13 @@
*******************************************************************************/
package org.jetbrains.kotlin.core.resolve

import kotlinx.coroutines.Deferred
import kotlinx.coroutines.async
import org.eclipse.core.resources.IProject
import org.jetbrains.kotlin.core.model.KotlinAnalysisFileCache
import org.jetbrains.kotlin.core.model.KotlinEnvironment
import org.jetbrains.kotlin.core.model.getEnvironment
import org.jetbrains.kotlin.core.utils.ProjectUtils
import org.jetbrains.kotlin.psi.KtFile

object KotlinAnalyzer {
Expand All @@ -34,27 +37,25 @@ object KotlinAnalyzer {
files.size == 1 -> analyzeFile(files.single())

else -> {
val environment = getEnvironment(files.first().project)
if (environment == null) {
val environment = getEnvironment(files.first().project) ?:
throw IllegalStateException("There is no environment for project: ${files.first().project}")
}

if (environment !is KotlinEnvironment) {

environment as? KotlinEnvironment ?:
throw IllegalStateException("Only KotlinEnvironment can be used to analyze several files")
}


analyzeFiles(environment, files)
}
}
}

fun analyzeProject(eclipseProject: IProject): AnalysisResultWithProvider {
val environment = KotlinEnvironment.getEnvironment(eclipseProject)
return analyzeFiles(environment, emptyList())
}

fun analyzeProjectAsync(project: IProject) : Deferred<AnalysisResultWithProvider> =
KotlinCoroutinesScope.async {
analyzeFiles(
KotlinEnvironment.getEnvironment(project),
ProjectUtils.getSourceFiles(project))
}

private fun analyzeFiles(kotlinEnvironment: KotlinEnvironment,
filesToAnalyze: Collection<KtFile>): AnalysisResultWithProvider {
return EclipseAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(kotlinEnvironment, filesToAnalyze)
}
filesToAnalyze: Collection<KtFile>): AnalysisResultWithProvider =
EclipseAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(kotlinEnvironment, filesToAnalyze)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.jetbrains.kotlin.core.resolve

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.joinAll
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlin.coroutines.CoroutineContext

object KotlinCoroutinesScope : CoroutineScope {

private val job: Job by lazy { SupervisorJob() }

override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Default

@JvmStatic
fun join() = runBlocking {
job.children.toList().joinAll()
}
}
Loading

0 comments on commit 3f93585

Please sign in to comment.