New J2K: Add IDEA integration of J2k

This commit is contained in:
Ilya Kirillov
2019-03-13 14:46:02 +03:00
committed by Ilya Kirillov
parent a4e3e7abcc
commit a7d2238af4
25 changed files with 378 additions and 209 deletions
@@ -28,8 +28,7 @@ class AfterConversionPass(val project: Project, val postProcessor: PostProcessor
marker.isGreedyToLeft = true
marker.isGreedyToRight = true
marker
}
else {
} else {
null
}
@@ -0,0 +1,29 @@
package org.jetbrains.kotlin.j2k
import com.intellij.openapi.extensions.AbstractExtensionPointBean
import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.registry.Registry
abstract class J2kConverterExtension : AbstractExtensionPointBean() {
abstract val isNewJ2k: Boolean
abstract fun createJavaToKotlinConverter(
project: Project,
settings: ConverterSettings,
services: JavaToKotlinConverterServices
): JavaToKotlinConverter
abstract fun createPostProcessor(formatCode: Boolean, settings: ConverterSettings = ConverterSettings.defaultSettings): PostProcessor
companion object {
private fun useNewJ2k() = Registry.`is`("kotlin.use.new.j2k", false)
val EP_NAME = ExtensionPointName.create<J2kConverterExtension>("org.jetbrains.kotlin.j2kConverterExtension")
val extension: J2kConverterExtension
get() = EP_NAME.extensions.first { it.isNewJ2k == useNewJ2k() }
}
}
@@ -47,35 +47,37 @@ enum class ParseContext {
CODE_BLOCK
}
class JavaToKotlinConverter(
private val project: Project,
private val settings: ConverterSettings,
private val services: JavaToKotlinConverterServices
) {
interface ExternalCodeProcessing {
fun prepareWriteOperation(progress: ProgressIndicator): () -> Unit
}
data class ElementResult(val text: String, val importsToAdd: Set<FqName>, val parseContext: ParseContext)
data class Result(val results: List<ElementResult?>, val externalCodeProcessing: ExternalCodeProcessing?)
data class FilesResult(val results: List<String>, val externalCodeProcessing: ExternalCodeProcessing?)
abstract class JavaToKotlinConverter(private val project: Project) {
protected abstract fun elementsToKotlin(inputElements: List<PsiElement>, processor: WithProgressProcessor): Result
private val LOG = Logger.getInstance("#org.jetbrains.kotlin.j2k.JavaToKotlinConverter")
interface ExternalCodeProcessing {
fun prepareWriteOperation(progress: ProgressIndicator): () -> Unit
}
data class ElementResult(val text: String, val importsToAdd: Set<FqName>, val parseContext: ParseContext)
data class Result(val results: List<ElementResult?>, val externalCodeProcessing: ExternalCodeProcessing?)
data class FilesResult(val results: List<String>, val externalCodeProcessing: ExternalCodeProcessing?)
fun filesToKotlin(files: List<PsiJavaFile>, postProcessor: PostProcessor, progress: ProgressIndicator = EmptyProgressIndicator()): FilesResult {
fun filesToKotlin(
files: List<PsiJavaFile>,
postProcessor: PostProcessor,
progress: ProgressIndicator = EmptyProgressIndicator()
): FilesResult {
val withProgressProcessor = WithProgressProcessor(progress, files)
val (results, externalCodeProcessing) = ApplicationManager.getApplication().runReadAction(Computable {
elementsToKotlin(files, withProgressProcessor)
})
val texts = withProgressProcessor.processItems(0.5, results.withIndex()) { pair ->
val (i, result) = pair
try {
val kotlinFile = ApplicationManager.getApplication().runReadAction(Computable {
KtPsiFactory(project).createAnalyzableFile("dummy.kt", result!!.text, files[i])
KtPsiFactory(project).createFileWithLightClassSupport("dummy.kt", result!!.text, files[i])
})
result!!.importsToAdd.forEach { postProcessor.insertImport(kotlinFile, it) }
@@ -83,11 +85,9 @@ class JavaToKotlinConverter(
AfterConversionPass(project, postProcessor).run(kotlinFile, range = null)
kotlinFile.text
}
catch(e: ProcessCanceledException) {
} catch (e: ProcessCanceledException) {
throw e
}
catch(t: Throwable) {
} catch (t: Throwable) {
LOG.error(t)
result!!.text
}
@@ -99,16 +99,22 @@ class JavaToKotlinConverter(
fun elementsToKotlin(inputElements: List<PsiElement>): Result {
return elementsToKotlin(inputElements, WithProgressProcessor.DEFAULT)
}
}
private fun elementsToKotlin(inputElements: List<PsiElement>, processor: WithProgressProcessor): Result {
class OldJavaToKotlinConverter(
project: Project,
private val settings: ConverterSettings,
private val services: JavaToKotlinConverterServices
) : JavaToKotlinConverter(project) {
protected override fun elementsToKotlin(inputElements: List<PsiElement>, processor: WithProgressProcessor): Result {
try {
val usageProcessings = LinkedHashMap<PsiElement, MutableCollection<UsageProcessing>>()
val usageProcessingCollector: (UsageProcessing) -> Unit = {
usageProcessings.getOrPut(it.targetElement, { ArrayList() }).add(it)
}
fun inConversionScope(element: PsiElement)
= inputElements.any { it.isAncestor(element, strict = false) }
fun inConversionScope(element: PsiElement) = inputElements.any { it.isAncestor(element, strict = false) }
val intermediateResults = processor.processItems(0.25, inputElements) { inputElement ->
@@ -127,14 +133,12 @@ class JavaToKotlinConverter(
val externalCodeProcessing = buildExternalCodeProcessing(usageProcessings, ::inConversionScope)
return Result(results, externalCodeProcessing)
}
catch(e: ElementCreationStackTraceRequiredException) {
} catch (e: ElementCreationStackTraceRequiredException) {
// if we got this exception then we need to turn element creation stack traces on to get better diagnostic
Element.saveCreationStacktraces = true
try {
return elementsToKotlin(inputElements)
}
finally {
} finally {
Element.saveCreationStacktraces = false
}
}
@@ -162,7 +166,7 @@ class JavaToKotlinConverter(
.groupBy { it.targetElement }
if (map.isEmpty()) return null
return object: ExternalCodeProcessing {
return object : ExternalCodeProcessing {
override fun prepareWriteOperation(progress: ProgressIndicator): () -> Unit {
val refs = ArrayList<ReferenceInfo>()
@@ -183,7 +187,8 @@ class JavaToKotlinConverter(
.filterNot { inConversionScope(it.element) }
.mapTo(refs) { ReferenceInfo(it, psiElement, it.element.containingFile, processings) }
},
ProgressPortionReporter(progress, i / map.size.toDouble(), 1.0 / map.size))
ProgressPortionReporter(progress, i / map.size.toDouble(), 1.0 / map.size)
)
}
@@ -232,86 +237,89 @@ class JavaToKotlinConverter(
return -info1.offset.compareTo(info2.offset)
}
}
}
private class WithProgressProcessor(private val progress: ProgressIndicator?, private val files: List<PsiJavaFile>?) {
companion object {
val DEFAULT = WithProgressProcessor(null, null)
}
private val progressText = "Converting Java to Kotlin"
private val fileCount = files?.size ?: 0
private val fileCountText = fileCount.toString() + " " + if (fileCount > 1) "files" else "file"
private var fraction = 0.0
private var pass = 1
fun <TInputItem, TOutputItem> processItems(
fractionPortion: Double,
inputItems: Iterable<TInputItem>,
processItem: (TInputItem) -> TOutputItem
): List<TOutputItem> {
val outputItems = ArrayList<TOutputItem>()
// we use special process with EmptyProgressIndicator to avoid changing text in our progress by inheritors search inside etc
ProgressManager.getInstance().runProcess(
{
progress?.text = "$progressText ($fileCountText) - pass $pass of 3"
for ((i, item) in inputItems.withIndex()) {
progress?.checkCanceled()
progress?.fraction = fraction + fractionPortion * i / fileCount
progress?.text2 = files!![i].virtualFile.presentableUrl
outputItems.add(processItem(item))
}
pass++
fraction += fractionPortion
},
EmptyProgressIndicator())
return outputItems
}
class WithProgressProcessor(private val progress: ProgressIndicator?, private val files: List<PsiJavaFile>?) {
companion object {
val DEFAULT = WithProgressProcessor(null, null)
}
private class ProgressPortionReporter(
indicator: ProgressIndicator,
private val start: Double,
private val portion: Double
) : DelegatingProgressIndicator(indicator) {
private val progressText = "Converting Java to Kotlin"
private val fileCount = files?.size ?: 0
private val fileCountText = fileCount.toString() + " " + if (fileCount > 1) "files" else "file"
private var fraction = 0.0
private var pass = 1
init {
fraction = 0.0
}
fun <TInputItem, TOutputItem> processItems(
fractionPortion: Double,
inputItems: Iterable<TInputItem>,
processItem: (TInputItem) -> TOutputItem
): List<TOutputItem> {
val outputItems = ArrayList<TOutputItem>()
// we use special process with EmptyProgressIndicator to avoid changing text in our progress by inheritors search inside etc
ProgressManager.getInstance().runProcess(
{
progress?.text = "$progressText ($fileCountText) - pass $pass of 3"
override fun start() {
fraction = 0.0
}
for ((i, item) in inputItems.withIndex()) {
progress?.checkCanceled()
progress?.fraction = fraction + fractionPortion * i / fileCount
override fun stop() {
fraction = portion
}
progress?.text2 = files!![i].virtualFile.presentableUrl
override fun setFraction(fraction: Double) {
super.setFraction(start + (fraction * portion))
}
outputItems.add(processItem(item))
}
override fun getFraction(): Double {
return (super.getFraction() - start) / portion
}
pass++
fraction += fractionPortion
},
EmptyProgressIndicator()
)
return outputItems
}
}
override fun setText(text: String?) {
}
private class ProgressPortionReporter(
indicator: ProgressIndicator,
private val start: Double,
private val portion: Double
) : DelegatingProgressIndicator(indicator) {
override fun setText2(text: String?) {
}
init {
fraction = 0.0
}
// Copied from com.intellij.ide.util.DelegatingProgressIndicator
private open class DelegatingProgressIndicator : WrappedProgressIndicator, StandardProgressIndicator {
protected val delegate: ProgressIndicator
override fun start() {
fraction = 0.0
}
constructor(indicator: ProgressIndicator) {
delegate = indicator
}
override fun stop() {
fraction = portion
}
override fun setFraction(fraction: Double) {
super.setFraction(start + (fraction * portion))
}
override fun getFraction(): Double {
return (super.getFraction() - start) / portion
}
override fun setText(text: String?) {
}
override fun setText2(text: String?) {
}
}
// Copied from com.intellij.ide.util.DelegatingProgressIndicator
open class DelegatingProgressIndicator : WrappedProgressIndicator, StandardProgressIndicator {
protected val delegate: ProgressIndicator
constructor(indicator: ProgressIndicator) {
delegate = indicator
}
constructor() {
// BUNCH: 181
@@ -320,49 +328,48 @@ class JavaToKotlinConverter(
delegate = indicator ?: EmptyProgressIndicator()
}
override fun start() = delegate.start()
override fun stop() = delegate.stop()
override fun isRunning() = delegate.isRunning
override fun cancel() = delegate.cancel()
override fun isCanceled() = delegate.isCanceled
override fun start() = delegate.start()
override fun stop() = delegate.stop()
override fun isRunning() = delegate.isRunning
override fun cancel() = delegate.cancel()
override fun isCanceled() = delegate.isCanceled
override fun setText(text: String?) {
delegate.text = text
}
override fun getText() = delegate.text
override fun setText2(text: String?) {
delegate.text2 = text
}
override fun getText2() = delegate.text2
override fun getFraction() = delegate.fraction
override fun setFraction(fraction: Double) {
delegate.fraction = fraction
}
override fun pushState() = delegate.pushState()
override fun popState() = delegate.popState()
override fun startNonCancelableSection() = delegate.startNonCancelableSection()
override fun finishNonCancelableSection() = delegate.finishNonCancelableSection()
override fun isModal() = delegate.isModal
override fun getModalityState() = delegate.modalityState
override fun setModalityProgress(modalityProgress: ProgressIndicator?) {
delegate.setModalityProgress(modalityProgress)
}
override fun isIndeterminate() = delegate.isIndeterminate
override fun setIndeterminate(indeterminate: Boolean) {
delegate.isIndeterminate = indeterminate
}
override fun checkCanceled() = delegate.checkCanceled()
override fun getOriginalProgressIndicator() = delegate
override fun isPopupWasShown() = delegate.isPopupWasShown
override fun isShowing() = delegate.isShowing
override fun setText(text: String?) {
delegate.text = text
}
}
override fun getText() = delegate.text
override fun setText2(text: String?) {
delegate.text2 = text
}
override fun getText2() = delegate.text2
override fun getFraction() = delegate.fraction
override fun setFraction(fraction: Double) {
delegate.fraction = fraction
}
override fun pushState() = delegate.pushState()
override fun popState() = delegate.popState()
override fun startNonCancelableSection() = delegate.startNonCancelableSection()
override fun finishNonCancelableSection() = delegate.finishNonCancelableSection()
override fun isModal() = delegate.isModal
override fun getModalityState() = delegate.modalityState
override fun setModalityProgress(modalityProgress: ProgressIndicator?) {
delegate.setModalityProgress(modalityProgress)
}
override fun isIndeterminate() = delegate.isIndeterminate
override fun setIndeterminate(indeterminate: Boolean) {
delegate.isIndeterminate = indeterminate
}
override fun checkCanceled() = delegate.checkCanceled()
override fun getOriginalProgressIndicator() = delegate
override fun isPopupWasShown() = delegate.isPopupWasShown
override fun isShowing() = delegate.isShowing
}
@@ -45,7 +45,7 @@ object JavaToKotlinTranslator {
fun generateKotlinCode(javaCode: String, project: Project): String {
val file = createFile(javaCode, project)
if (file is PsiJavaFile) {
val converter = JavaToKotlinConverter(file.project, ConverterSettings.defaultSettings, EmptyJavaToKotlinServices)
val converter = OldJavaToKotlinConverter(file.project, ConverterSettings.defaultSettings, EmptyJavaToKotlinServices)
return prettify(converter.elementsToKotlin(listOf(file)).results.single()!!.text) //TODO: imports
}
return ""
@@ -53,7 +53,7 @@ abstract class AbstractJavaToKotlinConverterMultiFileTest : AbstractJavaToKotlin
assert(psiFile is PsiJavaFile || psiFile is KtFile)
}
val converter = JavaToKotlinConverter(project, ConverterSettings.defaultSettings, IdeaJavaToKotlinServices)
val converter = OldJavaToKotlinConverter(project, ConverterSettings.defaultSettings, IdeaJavaToKotlinServices)
val (results, externalCodeProcessor) = converter.filesToKotlin(psiFilesToConvert, J2kPostProcessor(formatCode = true))
val process = externalCodeProcessor?.prepareWriteOperation(EmptyProgressIndicator())
@@ -110,7 +110,7 @@ abstract class AbstractJavaToKotlinConverterSingleFileTest : AbstractJavaToKotli
protected open fun fileToKotlin(text: String, settings: ConverterSettings, project: Project): String {
val file = createJavaFile(text)
val converter = JavaToKotlinConverter(project, settings, IdeaJavaToKotlinServices)
val converter = OldJavaToKotlinConverter(project, settings, IdeaJavaToKotlinServices)
return converter.filesToKotlin(listOf(file), J2kPostProcessor(formatCode = true)).results.single()
}