KT-5289 J2K: Should show progress when conversion takes time

#KT-5289 Fixed
This commit is contained in:
Valentin Kipyatkov
2015-04-02 17:45:25 +03:00
parent b3ef334a0e
commit 4b32db5c5e
8 changed files with 122 additions and 71 deletions
@@ -16,45 +16,64 @@
package org.jetbrains.kotlin.idea.actions package org.jetbrains.kotlin.idea.actions
import com.intellij.codeInsight.actions.ReformatCodeProcessor
import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.CommonDataKeys import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.command.CommandProcessor import com.intellij.openapi.command.CommandProcessor
import com.intellij.openapi.fileEditor.FileEditorManager import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.ex.MessagesEx
import com.intellij.openapi.vfs.CharsetToolkit
import com.intellij.openapi.vfs.VfsUtilCore
import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.VirtualFileVisitor
import com.intellij.psi.PsiJavaFile
import com.intellij.psi.PsiManager
import com.intellij.psi.codeStyle.CodeStyleManager
import org.jetbrains.kotlin.idea.j2k.IdeaResolverForConverter
import org.jetbrains.kotlin.idea.j2k.J2kPostProcessor
import org.jetbrains.kotlin.j2k.ConverterSettings import org.jetbrains.kotlin.j2k.ConverterSettings
import org.jetbrains.kotlin.j2k.FilesConversionScope import org.jetbrains.kotlin.j2k.FilesConversionScope
import org.jetbrains.kotlin.j2k.IdeaReferenceSearcher import org.jetbrains.kotlin.j2k.IdeaReferenceSearcher
import org.jetbrains.kotlin.j2k.JavaToKotlinConverter import org.jetbrains.kotlin.j2k.JavaToKotlinConverter
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiJavaFile
import com.intellij.psi.PsiManager
import java.util.ArrayList
import com.intellij.openapi.application.ApplicationManager
import com.intellij.psi.codeStyle.CodeStyleManager
import java.io.IOException
import com.intellij.openapi.ui.ex.MessagesEx
import org.jetbrains.kotlin.idea.j2k.J2kPostProcessor
import com.intellij.openapi.vfs.CharsetToolkit
import org.jetbrains.kotlin.psi.JetFile import org.jetbrains.kotlin.psi.JetFile
import org.jetbrains.kotlin.utils.addIfNotNull import org.jetbrains.kotlin.utils.addIfNotNull
import com.intellij.openapi.vfs.VfsUtilCore import java.io.IOException
import com.intellij.openapi.vfs.VirtualFileVisitor import java.util.ArrayList
import org.jetbrains.kotlin.idea.j2k.IdeaResolverForConverter
public class JavaToKotlinAction : AnAction() { public class JavaToKotlinAction : AnAction() {
override fun actionPerformed(e: AnActionEvent) { override fun actionPerformed(e: AnActionEvent) {
val selectedJavaFiles = selectedJavaFiles(e).toList() val javaFiles = selectedJavaFiles(e).toList()
val project = CommonDataKeys.PROJECT.getData(e.getDataContext())!! val project = CommonDataKeys.PROJECT.getData(e.getDataContext())!!
var convertedTexts: List<String>? = null
fun convert() {
val converter = JavaToKotlinConverter(project, ConverterSettings.defaultSettings, FilesConversionScope(javaFiles), IdeaReferenceSearcher, IdeaResolverForConverter)
convertedTexts = converter.elementsToKotlin(javaFiles.map { it to J2kPostProcessor(it, formatCode = true) },
ProgressManager.getInstance().getProgressIndicator())
}
if (!ProgressManager.getInstance().runProcessWithProgressSynchronously(
{
ApplicationManager.getApplication().runReadAction(::convert)
},
"Converting Java to Kotlin",
true,
project)) return
CommandProcessor.getInstance().executeCommand(project, object : Runnable { CommandProcessor.getInstance().executeCommand(project, object : Runnable {
override fun run() { override fun run() {
val newFiles = convertFiles(selectedJavaFiles, project) CommandProcessor.getInstance().markCurrentCommandAsGlobal(project)
deleteFiles(selectedJavaFiles)
reformatFiles(newFiles, project)
if (newFiles.size() == 1) { val newFiles = saveResults(javaFiles, convertedTexts!!, project)
FileEditorManager.getInstance(project).openFile(newFiles.single(), true) deleteFiles(javaFiles)
newFiles.singleOrNull()?.let {
FileEditorManager.getInstance(project).openFile(it, true)
} }
} }
}, "Convert files from Java to Kotlin", null) }, "Convert files from Java to Kotlin", null)
@@ -65,16 +84,16 @@ public class JavaToKotlinAction : AnAction() {
e.getPresentation().setEnabled(enabled) e.getPresentation().setEnabled(enabled)
} }
private fun selectedJavaFiles(e: AnActionEvent): Stream<PsiJavaFile> { private fun selectedJavaFiles(e: AnActionEvent): Sequence<PsiJavaFile> {
val virtualFiles = e.getData(CommonDataKeys.VIRTUAL_FILE_ARRAY) ?: return streamOf() val virtualFiles = e.getData(CommonDataKeys.VIRTUAL_FILE_ARRAY) ?: return sequenceOf()
val project = CommonDataKeys.PROJECT.getData(e.getDataContext()) ?: return streamOf() val project = CommonDataKeys.PROJECT.getData(e.getDataContext()) ?: return sequenceOf()
return allJavaFiles(virtualFiles, project) return allJavaFiles(virtualFiles, project)
} }
private fun allJavaFiles(filesOrDirs: Array<VirtualFile>, project: Project): Stream<PsiJavaFile> { private fun allJavaFiles(filesOrDirs: Array<VirtualFile>, project: Project): Sequence<PsiJavaFile> {
val manager = PsiManager.getInstance(project) val manager = PsiManager.getInstance(project)
return allFiles(filesOrDirs) return allFiles(filesOrDirs)
.stream() .sequence()
.map { manager.findFile(it) as? PsiJavaFile } .map { manager.findFile(it) as? PsiJavaFile }
.filterNotNull() .filterNotNull()
} }
@@ -92,22 +111,7 @@ public class JavaToKotlinAction : AnAction() {
return result return result
} }
private fun reformatFiles(kotlinFiles: List<VirtualFile>, project: Project) { private fun saveResults(javaFiles: List<PsiJavaFile>, convertedTexts: List<String>, project: Project): List<VirtualFile> {
for (file in kotlinFiles) {
ApplicationManager.getApplication().runWriteAction {
val jetFile = PsiManager.getInstance(project).findFile(file) as? JetFile
if (jetFile != null) {
CodeStyleManager.getInstance(project).reformat(jetFile)
}
}
}
}
private fun convertFiles(javaFiles: List<PsiJavaFile>, project: Project): List<VirtualFile> {
val converter = JavaToKotlinConverter(project, ConverterSettings.defaultSettings, FilesConversionScope(javaFiles), IdeaReferenceSearcher, IdeaResolverForConverter)
val convertedTexts = converter.elementsToKotlin(javaFiles.map { it to J2kPostProcessor(it) })
val result = ArrayList<VirtualFile>() val result = ArrayList<VirtualFile>()
for ((i, psiFile) in javaFiles.withIndex()) { for ((i, psiFile) in javaFiles.withIndex()) {
ApplicationManager.getApplication().runWriteAction { ApplicationManager.getApplication().runWriteAction {
@@ -127,7 +127,7 @@ public class ConvertJavaCopyPastePostProcessor() : CopyPastePostProcessor<TextBl
val elementToConvert = findTopMostParentWhollyInRange(currentRange, leafElement) val elementToConvert = findTopMostParentWhollyInRange(currentRange, leafElement)
val unconvertedPrefix = fileText.substring(currentRange.start, elementToConvert.range.start) val unconvertedPrefix = fileText.substring(currentRange.start, elementToConvert.range.start)
result.append(unconvertedPrefix) result.append(unconvertedPrefix)
val converted = converter.elementsToKotlin(listOf(elementToConvert to J2kPostProcessor(fileCopiedTo)))[0] val converted = converter.elementsToKotlin(listOf(elementToConvert to J2kPostProcessor(fileCopiedTo, formatCode = false)))[0]
if (converted.isNotEmpty()) { if (converted.isNotEmpty()) {
result.append(converted) result.append(converted)
} }
@@ -21,10 +21,13 @@ import com.intellij.formatting.FormattingModelBuilder;
import com.intellij.formatting.FormattingModelProvider; import com.intellij.formatting.FormattingModelProvider;
import com.intellij.formatting.Indent; import com.intellij.formatting.Indent;
import com.intellij.lang.ASTNode; import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.impl.DocumentImpl;
import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile; import com.intellij.psi.PsiFile;
import com.intellij.psi.codeStyle.CodeStyleSettings; import com.intellij.psi.codeStyle.CodeStyleSettings;
import com.intellij.psi.formatter.FormattingDocumentModelImpl;
import com.intellij.psi.formatter.PsiBasedFormattingModel;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.idea.JetLanguage; import org.jetbrains.kotlin.idea.JetLanguage;
@@ -37,8 +40,15 @@ public class JetFormattingModelBuilder implements FormattingModelBuilder {
containingFile.getNode(), NodeAlignmentStrategy.getNullStrategy(), Indent.getNoneIndent(), null, settings, containingFile.getNode(), NodeAlignmentStrategy.getNullStrategy(), Indent.getNoneIndent(), null, settings,
FormatterPackage.createSpacingBuilder(settings)); FormatterPackage.createSpacingBuilder(settings));
return FormattingModelProvider.createFormattingModelForPsiFile( //TODO: this is temporary code to allow formatting non-physical files in non-UI thread (used by conversion from Java to Kotlin)
element.getContainingFile(), block, settings); // it's needed until IDEA's issue with this document being created with wrong threading policy is fixed
if (!element.isPhysical()) {
FormattingDocumentModelImpl formattingDocumentModel =
new FormattingDocumentModelImpl(new DocumentImpl(containingFile.getViewProvider().getContents(), true), containingFile);
return new PsiBasedFormattingModel(containingFile, block, formattingDocumentModel);
}
return FormattingModelProvider.createFormattingModelForPsiFile(element.getContainingFile(), block, settings);
} }
@Override @Override
@@ -16,7 +16,10 @@
package org.jetbrains.kotlin.idea.j2k package org.jetbrains.kotlin.idea.j2k
import com.intellij.openapi.editor.impl.DocumentImpl
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.psi.codeStyle.CodeStyleManager
import com.intellij.psi.search.LocalSearchScope import com.intellij.psi.search.LocalSearchScope
import com.intellij.psi.search.searches.ReferencesSearch import com.intellij.psi.search.searches.ReferencesSearch
import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.diagnostics.Diagnostic
@@ -31,9 +34,11 @@ import org.jetbrains.kotlin.idea.quickfix.RemoveRightPartOfBinaryExpressionFix
import org.jetbrains.kotlin.j2k.PostProcessor import org.jetbrains.kotlin.j2k.PostProcessor
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.BindingContext
import java.lang.reflect.Field
import java.lang.reflect.Modifier
import java.util.ArrayList import java.util.ArrayList
public class J2kPostProcessor(override val contextToAnalyzeIn: PsiElement) : PostProcessor { public class J2kPostProcessor(override val contextToAnalyzeIn: PsiElement, private val formatCode: Boolean) : PostProcessor {
override fun analyzeFile(file: JetFile): BindingContext { override fun analyzeFile(file: JetFile): BindingContext {
return file.analyzeFullyAndGetResult().bindingContext return file.analyzeFullyAndGetResult().bindingContext
} }
@@ -109,5 +114,9 @@ public class J2kPostProcessor(override val contextToAnalyzeIn: PsiElement) : Pos
for (typeArgs in redundantTypeArgs) { for (typeArgs in redundantTypeArgs) {
typeArgs.delete() typeArgs.delete()
} }
if (formatCode) {
CodeStyleManager.getInstance(contextToAnalyzeIn.getProject()).reformat(file)
}
} }
} }
@@ -26,6 +26,7 @@ import org.jetbrains.kotlin.psi.JetProperty
class AfterConversionPass(val project: Project, val postProcessor: PostProcessor) { class AfterConversionPass(val project: Project, val postProcessor: PostProcessor) {
public fun run(kotlinCode: String): String { public fun run(kotlinCode: String): String {
//TODO: it's more correct to analyze all converted files together
val kotlinFile = JetPsiFactory(project).createAnalyzableFile( val kotlinFile = JetPsiFactory(project).createAnalyzableFile(
"fileForAfterConversionPass.kt", kotlinCode, postProcessor.contextToAnalyzeIn "fileForAfterConversionPass.kt", kotlinCode, postProcessor.contextToAnalyzeIn
) )
@@ -17,9 +17,13 @@
package org.jetbrains.kotlin.j2k package org.jetbrains.kotlin.j2k
import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.progress.EmptyProgressIndicator
import com.intellij.openapi.progress.ProcessCanceledException import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiJavaFile import com.intellij.psi.PsiJavaFile
import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.diagnostics.Errors
@@ -73,29 +77,68 @@ public class JavaToKotlinConverter(private val project: Project,
private val resolverForConverter: ResolverForConverter) { private val resolverForConverter: ResolverForConverter) {
private val LOG = Logger.getInstance("#org.jetbrains.kotlin.j2k.JavaToKotlinConverter") private val LOG = Logger.getInstance("#org.jetbrains.kotlin.j2k.JavaToKotlinConverter")
public fun elementsToKotlin(psiElementsAndProcessors: List<Pair<PsiElement, PostProcessor?>>): List<String> { public fun elementsToKotlin(
psiElementsAndProcessors: List<Pair<PsiElement, PostProcessor?>>,
progress: ProgressIndicator = EmptyProgressIndicator()
): List<String> {
try { try {
val intermediateResults = ArrayList<((Map<PsiElement, UsageProcessing>) -> String)?>(psiElementsAndProcessors.size()) val elementCount = psiElementsAndProcessors.size()
val intermediateResults = ArrayList<((Map<PsiElement, UsageProcessing>) -> String)?>(elementCount)
val usageProcessings = HashMap<PsiElement, UsageProcessing>() val usageProcessings = HashMap<PsiElement, UsageProcessing>()
val usageProcessingCollector: (UsageProcessing) -> Unit = { usageProcessing -> val usageProcessingCollector: (UsageProcessing) -> Unit = { usageProcessing ->
assert(!usageProcessings.containsKey(usageProcessing.targetElement)) assert(!usageProcessings.containsKey(usageProcessing.targetElement))
{ "Duplicated UsageProcessing for target element ${usageProcessing.targetElement}" } { "Duplicated UsageProcessing for target element ${usageProcessing.targetElement}" }
usageProcessings.put(usageProcessing.targetElement, usageProcessing) usageProcessings.put(usageProcessing.targetElement, usageProcessing)
} }
for ((psiElement, postProcessor) in psiElementsAndProcessors) {
val progressText = "Converting Java to Kotlin"
val fileCountText = elementCount.toString() + " " + if (elementCount > 1) "files" else "file"
var fraction = 0.0
var pass = 1
fun processFilesWithProgress(passFraction: Double, processFile: (Int) -> Unit) {
// we use special process with EmptyProgressIndicator to avoid changing text in our progress by inheritors search inside etc
ProgressManager.getInstance().runProcess(
{
progress.setText("$progressText ($fileCountText) - pass $pass of 3")
val filesCount = psiElementsAndProcessors.indices
for (i in filesCount) {
progress.checkCanceled()
progress.setFraction(fraction + passFraction * i / elementCount)
val psiFile = psiElementsAndProcessors[i].first as? PsiFile
if (psiFile != null) {
progress.setText2(psiFile.getVirtualFile().getPresentableUrl())
}
processFile(i)
}
pass++
fraction += passFraction
},
EmptyProgressIndicator())
}
processFilesWithProgress(0.25) { i ->
val psiElement = psiElementsAndProcessors[i].first
val postProcessor = psiElementsAndProcessors[i].second
val converter = Converter.create(psiElement, settings, conversionScope, referenceSearcher, resolverForConverter, postProcessor, usageProcessingCollector) val converter = Converter.create(psiElement, settings, conversionScope, referenceSearcher, resolverForConverter, postProcessor, usageProcessingCollector)
val result = converter.convert() val result = converter.convert()
intermediateResults.add(result) intermediateResults.add(result)
} }
val results = ArrayList<String>(psiElementsAndProcessors.size()) val results = ArrayList<String>(elementCount)
for ((i, result) in intermediateResults.withIndex()) { processFilesWithProgress(0.25) { i ->
val result = intermediateResults[i]
results.add(if (result != null) result(usageProcessings) else "") results.add(if (result != null) result(usageProcessings) else "")
intermediateResults[i] = null // to not hold unused objects in the heap intermediateResults[i] = null // to not hold unused objects in the heap
} }
val finalResults = ArrayList<String>(psiElementsAndProcessors.size()) val finalResults = ArrayList<String>(elementCount)
for ((i, result) in results.withIndex()) { processFilesWithProgress(0.5) { i ->
val result = results[i]
val postProcessor = psiElementsAndProcessors[i].second val postProcessor = psiElementsAndProcessors[i].second
if (postProcessor != null) { if (postProcessor != null) {
try { try {
@@ -113,6 +156,7 @@ public class JavaToKotlinConverter(private val project: Project,
finalResults.add(result) finalResults.add(result)
} }
} }
return finalResults return finalResults
} }
catch(e: ElementCreationStackTraceRequiredException) { catch(e: ElementCreationStackTraceRequiredException) {
@@ -35,7 +35,7 @@ public abstract class AbstractJavaToKotlinConverterMultiFileTest : AbstractJavaT
val project = LightPlatformTestCase.getProject()!! val project = LightPlatformTestCase.getProject()!!
val psiManager = PsiManager.getInstance(project) val psiManager = PsiManager.getInstance(project)
val javaFiles = File(dirPath).listFiles {(file, name): Boolean -> name.endsWith(".java") } val javaFiles = File(dirPath).listFiles { file, name -> name.endsWith(".java") }
val psiFiles = ArrayList<PsiJavaFile>() val psiFiles = ArrayList<PsiJavaFile>()
for (javaFile: File in javaFiles) { for (javaFile: File in javaFiles) {
val virtualFile = addFile(javaFile, "test") val virtualFile = addFile(javaFile, "test")
@@ -44,8 +44,7 @@ public abstract class AbstractJavaToKotlinConverterMultiFileTest : AbstractJavaT
} }
val converter = JavaToKotlinConverter(project, ConverterSettings.defaultSettings, FilesConversionScope(psiFiles), IdeaReferenceSearcher, IdeaResolverForConverter) val converter = JavaToKotlinConverter(project, ConverterSettings.defaultSettings, FilesConversionScope(psiFiles), IdeaReferenceSearcher, IdeaResolverForConverter)
val results: List<String> = converter.elementsToKotlin(psiFiles.map { it to J2kPostProcessor(it) }) val results: List<String> = converter.elementsToKotlin(psiFiles.map { it to J2kPostProcessor(it, formatCode = true) })
.map { reformat(it, project) }
fun expectedFile(i: Int) = File(javaFiles[i].getPath().replace(".java", ".kt")) fun expectedFile(i: Int) = File(javaFiles[i].getPath().replace(".java", ".kt"))
@@ -61,14 +60,6 @@ public abstract class AbstractJavaToKotlinConverterMultiFileTest : AbstractJavaT
} }
} }
private fun reformat(text: String, project: Project): String {
val convertedFile = JetTestUtils.createFile("converted", text, project)
WriteCommandAction.runWriteCommandAction(project) {
CodeStyleManager.getInstance(project)!!.reformat(convertedFile)
}
return convertedFile.getText()!!
}
override fun getProjectDescriptor() override fun getProjectDescriptor()
= JetWithJdkAndRuntimeLightProjectDescriptor.INSTANCE = JetWithJdkAndRuntimeLightProjectDescriptor.INSTANCE
} }
@@ -66,7 +66,6 @@ public abstract class AbstractJavaToKotlinConverterSingleFileTest : AbstractJava
} }
val rawConverted = when (prefix) { val rawConverted = when (prefix) {
"element" -> elementToKotlin(javaCode, settings, project)
"expression" -> expressionToKotlin(javaCode, settings, project) "expression" -> expressionToKotlin(javaCode, settings, project)
"statement" -> statementToKotlin(javaCode, settings, project) "statement" -> statementToKotlin(javaCode, settings, project)
"method" -> methodToKotlin(javaCode, settings, project) "method" -> methodToKotlin(javaCode, settings, project)
@@ -105,17 +104,10 @@ public abstract class AbstractJavaToKotlinConverterSingleFileTest : AbstractJava
reformattedText reformattedText
} }
private fun elementToKotlin(text: String, settings: ConverterSettings, project: Project): String {
val fileWithText = createJavaFile(text)
val converter = JavaToKotlinConverter(project, settings, FilesConversionScope(listOf(fileWithText)), IdeaReferenceSearcher, IdeaResolverForConverter)
val element = fileWithText.getFirstChild()!!
return converter.elementsToKotlin(listOf(element to J2kPostProcessor(fileWithText)))[0]
}
private fun fileToKotlin(text: String, settings: ConverterSettings, project: Project): String { private fun fileToKotlin(text: String, settings: ConverterSettings, project: Project): String {
val file = createJavaFile(text) val file = createJavaFile(text)
val converter = JavaToKotlinConverter(project, settings, FilesConversionScope(listOf(file)), IdeaReferenceSearcher, IdeaResolverForConverter) val converter = JavaToKotlinConverter(project, settings, FilesConversionScope(listOf(file)), IdeaReferenceSearcher, IdeaResolverForConverter)
return converter.elementsToKotlin(listOf(file to J2kPostProcessor(file)))[0] return converter.elementsToKotlin(listOf(file to J2kPostProcessor(file, formatCode = false)))[0]
} }
private fun methodToKotlin(text: String, settings: ConverterSettings, project: Project): String { private fun methodToKotlin(text: String, settings: ConverterSettings, project: Project): String {