Conversion from Java on paste: post processing of code really works

This commit is contained in:
Valentin Kipyatkov
2015-04-03 02:48:39 +03:00
parent 2227452e7a
commit d0637ccaf4
17 changed files with 219 additions and 111 deletions
@@ -448,3 +448,34 @@ public fun JetStringTemplateExpression.getContentRange(): TextRange {
public fun JetStringTemplateExpression.isSingleQuoted(): Boolean public fun JetStringTemplateExpression.isSingleQuoted(): Boolean
= getNode().getFirstChildNode().getTextLength() == 1 = getNode().getFirstChildNode().getTextLength() == 1
public fun PsiFile.elementsInRange(range: TextRange): List<PsiElement> {
var offset = range.getStartOffset()
val result = ArrayList<PsiElement>()
while (offset < range.getEndOffset()) {
val currentRange = TextRange(offset, range.getEndOffset())
val leaf = findFirstLeafWhollyInRange(this, currentRange) ?: break
val element = leaf
.parents(withItself = true)
.first {
val parent = it.getParent()
parent == null || parent.getTextRange() !in currentRange
}
result.add(element)
offset = element.getTextRange().getEndOffset()
}
return result
}
private fun findFirstLeafWhollyInRange(file: PsiFile, range: TextRange): PsiElement? {
var element = file.findElementAt(range.getStartOffset()) ?: return null
var elementRange = element.getTextRange()
if (elementRange.getStartOffset() < range.getStartOffset()) {
element = file.findElementAt(elementRange.getEndOffset()) ?: return null
elementRange = element.getTextRange()
}
assert(elementRange.getStartOffset() >= range.getStartOffset())
return if (elementRange.getEndOffset() <= range.getEndOffset()) element else null
}
@@ -50,9 +50,10 @@ public class JavaToKotlinAction : AnAction() {
var convertedTexts: List<String>? = null var convertedTexts: List<String>? = null
fun convert() { fun convert() {
val converter = JavaToKotlinConverter(project, ConverterSettings.defaultSettings, IdeaReferenceSearcher, IdeaResolverForConverter) val converter = JavaToKotlinConverter(project, ConverterSettings.defaultSettings,
convertedTexts = converter.elementsToKotlin(javaFiles.map { it to J2kPostProcessor(it, formatCode = true) }, IdeaReferenceSearcher, IdeaResolverForConverter, J2kPostProcessor(formatCode = true))
ProgressManager.getInstance().getProgressIndicator()) val inputElements = javaFiles.map { JavaToKotlinConverter.InputElement(it, it) }
convertedTexts = converter.elementsToKotlin(inputElements, ProgressManager.getInstance().getProgressIndicator())
} }
if (!ProgressManager.getInstance().runProcessWithProgressSynchronously( if (!ProgressManager.getInstance().runProcessWithProgressSynchronously(
@@ -27,16 +27,17 @@ import com.intellij.openapi.project.DumbService
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Ref import com.intellij.openapi.util.Ref
import com.intellij.openapi.util.TextRange import com.intellij.openapi.util.TextRange
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.* import com.intellij.psi.*
import com.intellij.psi.codeStyle.CodeStyleManager import com.intellij.psi.codeStyle.CodeStyleManager
import org.jetbrains.kotlin.idea.editor.JetEditorOptions import org.jetbrains.kotlin.idea.editor.JetEditorOptions
import org.jetbrains.kotlin.idea.j2k.IdeaResolverForConverter import org.jetbrains.kotlin.idea.j2k.IdeaResolverForConverter
import org.jetbrains.kotlin.idea.j2k.J2kPostProcessor import org.jetbrains.kotlin.idea.j2k.J2kPostProcessor
import org.jetbrains.kotlin.j2k.AfterConversionPass
import org.jetbrains.kotlin.j2k.ConverterSettings import org.jetbrains.kotlin.j2k.ConverterSettings
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 org.jetbrains.kotlin.psi.JetFile import org.jetbrains.kotlin.psi.JetFile
import org.jetbrains.kotlin.psi.psiUtil.elementsInRange
import org.jetbrains.kotlin.psi.psiUtil.parents import org.jetbrains.kotlin.psi.psiUtil.parents
import java.awt.datatransfer.Transferable import java.awt.datatransfer.Transferable
import java.util.ArrayList import java.util.ArrayList
@@ -73,7 +74,7 @@ public class ConvertJavaCopyPastePostProcessor : CopyPastePostProcessor<TextBloc
fun doConversion(): String? { fun doConversion(): String? {
val sourceFile = PsiFileFactory.getInstance(project). val sourceFile = PsiFileFactory.getInstance(project).
createFileFromText(data.fileName, JavaLanguage.INSTANCE, data.fileText) as PsiJavaFile createFileFromText(data.fileName, JavaLanguage.INSTANCE, data.fileText) as PsiJavaFile
return convertCopiedCodeToKotlin(data, sourceFile, targetFile) return convertCopiedCodeToKotlin(data, sourceFile)
} }
var conversionResult: String? = null var conversionResult: String? = null
@@ -96,14 +97,15 @@ public class ConvertJavaCopyPastePostProcessor : CopyPastePostProcessor<TextBloc
val endOffsetAfterCopy = startOffset + conversionResult!!.length() val endOffsetAfterCopy = startOffset + conversionResult!!.length()
editor.getCaretModel().moveToOffset(endOffsetAfterCopy) editor.getCaretModel().moveToOffset(endOffsetAfterCopy)
CodeStyleManager.getInstance(project)!!.reformatText(targetFile, startOffset, endOffsetAfterCopy) PsiDocumentManager.getInstance(project).commitAllDocuments()
AfterConversionPass(project, J2kPostProcessor(formatCode = true)).run(targetFile, TextRange(startOffset, endOffsetAfterCopy))
conversionPerformed = true conversionPerformed = true
} }
} }
} }
private fun convertCopiedCodeToKotlin(code: CopiedCode, sourceFile: PsiJavaFile, targetFile: JetFile): String? { private fun convertCopiedCodeToKotlin(code: CopiedCode, sourceFile: PsiJavaFile): String? {
assert(code.startOffsets.size() == code.endOffsets.size(), "Must have the same size") assert(code.startOffsets.size() == code.endOffsets.size(), "Must have the same size")
val sourceFileText = code.fileText val sourceFileText = code.fileText
@@ -118,10 +120,12 @@ public class ConvertJavaCopyPastePostProcessor : CopyPastePostProcessor<TextBloc
sourceFile.getProject(), sourceFile.getProject(),
ConverterSettings.defaultSettings, ConverterSettings.defaultSettings,
IdeaReferenceSearcher, IdeaReferenceSearcher,
IdeaResolverForConverter IdeaResolverForConverter,
null
) )
val results = converter.elementsToKotlin(list.filterIsInstance<PsiElement>().map { it to J2kPostProcessor(targetFile, formatCode = false) }) val inputElements = list.filterIsInstance<PsiElement>().map { JavaToKotlinConverter.InputElement(it, null) }
val results = converter.elementsToKotlin(inputElements)
var resultIndex = 0 var resultIndex = 0
val convertedCodeBuilder = StringBuilder() val convertedCodeBuilder = StringBuilder()
@@ -157,41 +161,15 @@ public class ConvertJavaCopyPastePostProcessor : CopyPastePostProcessor<TextBloc
fileText: String, fileText: String,
range: TextRange range: TextRange
) { ) {
var currentRange = range val elements = file.elementsInRange(range)
while (!currentRange.isEmpty()) { if (elements.isEmpty()) {
val leaf = findFirstLeafWhollyInRange(file, currentRange) add(fileText.substring(range.getStartOffset(), range.getEndOffset()))
if (leaf == null) {
val unconvertedSuffix = fileText.substring(currentRange.start, currentRange.end)
add(unconvertedSuffix)
break
}
val elementToConvert = leaf
.parents(withItself = true)
.first {
val parent = it.getParent()
parent == null || parent.range !in currentRange
}
val elementToConvertRange = elementToConvert.range
val unconvertedPrefix = fileText.substring(currentRange.start, elementToConvertRange.start)
add(unconvertedPrefix)
add(elementToConvert)
currentRange = TextRange(elementToConvertRange.end, currentRange.end)
} }
} else {
add(fileText.substring(range.getStartOffset(), elements.first().getTextRange().getStartOffset()))
private fun findFirstLeafWhollyInRange(file: PsiJavaFile, range: TextRange): PsiElement? { addAll(elements)
var element = file.findElementAt(range.start) ?: return null add(fileText.substring(elements.last().getTextRange().getEndOffset(), range.getEndOffset()))
var elementRange = element.range
if (elementRange.start < range.start) {
element = file.findElementAt(elementRange.end) ?: return null
elementRange = element.range
} }
assert(elementRange.start >= range.start)
return if (elementRange.end <= range.end) element else null
} }
private fun okFromDialog(project: Project): Boolean { private fun okFromDialog(project: Project): Boolean {
@@ -16,15 +16,18 @@
package org.jetbrains.kotlin.idea.j2k package org.jetbrains.kotlin.idea.j2k
import com.intellij.openapi.editor.impl.DocumentImpl import com.intellij.openapi.editor.RangeMarker
import com.intellij.openapi.fileEditor.FileDocumentManager import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiComment
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.psi.PsiWhiteSpace
import com.intellij.psi.codeStyle.CodeStyleManager 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 com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.idea.caches.resolve.analyzeFullyAndGetResult import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.intentions.RemoveExplicitTypeArguments import org.jetbrains.kotlin.idea.intentions.RemoveExplicitTypeArguments
import org.jetbrains.kotlin.idea.intentions.SimplifyNegatedBinaryExpressionIntention import org.jetbrains.kotlin.idea.intentions.SimplifyNegatedBinaryExpressionIntention
import org.jetbrains.kotlin.idea.intentions.branchedTransformations.intentions.IfThenToElvisIntention import org.jetbrains.kotlin.idea.intentions.branchedTransformations.intentions.IfThenToElvisIntention
@@ -33,14 +36,19 @@ import org.jetbrains.kotlin.idea.quickfix.RemoveModifierFix
import org.jetbrains.kotlin.idea.quickfix.RemoveRightPartOfBinaryExpressionFix 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.psi.psiUtil.elementsInRange
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, private val formatCode: Boolean) : PostProcessor { public class J2kPostProcessor(private val formatCode: Boolean) : PostProcessor {
override fun analyzeFile(file: JetFile): BindingContext { override fun analyzeFile(file: JetFile, range: TextRange?): BindingContext {
return file.analyzeFullyAndGetResult().bindingContext val elements = if (range == null) {
listOf(file)
}
else {
file.elementsInRange(range).filterIsInstance<JetElement>()
}
return file.getResolutionFacade().analyzeFullyAndGetResult(elements).bindingContext
} }
override fun fixForProblem(problem: Diagnostic): (() -> Unit)? { override fun fixForProblem(problem: Diagnostic): (() -> Unit)? {
@@ -69,11 +77,35 @@ public class J2kPostProcessor(override val contextToAnalyzeIn: PsiElement, priva
} }
} }
override fun doAdditionalProcessing(file: JetFile) { private enum class RangeFilterResult {
SKIP
GO_INSIDE
PROCESS
}
override fun doAdditionalProcessing(file: JetFile, rangeMarker: RangeMarker?) {
fun rangeFilter(element: PsiElement): RangeFilterResult {
if (rangeMarker == null) return RangeFilterResult.PROCESS
if (!rangeMarker.isValid()) return RangeFilterResult.SKIP
val range = TextRange(rangeMarker.getStartOffset(), rangeMarker.getEndOffset())
val elementRange = element.getTextRange()!!
return when {
range.contains(elementRange) -> RangeFilterResult.PROCESS
range.intersects(elementRange) -> RangeFilterResult.GO_INSIDE
else -> RangeFilterResult.SKIP
}
}
val redundantTypeArgs = ArrayList<JetTypeArgumentList>() val redundantTypeArgs = ArrayList<JetTypeArgumentList>()
file.accept(object : JetTreeVisitorVoid(){ file.accept(object : JetTreeVisitorVoid(){
override fun visitElement(element: PsiElement) {
if (rangeFilter(element) != RangeFilterResult.SKIP) {
super.visitElement(element)
}
}
override fun visitTypeArgumentList(typeArgumentList: JetTypeArgumentList) { override fun visitTypeArgumentList(typeArgumentList: JetTypeArgumentList) {
if (RemoveExplicitTypeArguments().isApplicableTo(typeArgumentList)) { if (rangeFilter(typeArgumentList) == RangeFilterResult.PROCESS && RemoveExplicitTypeArguments().isApplicableTo(typeArgumentList)) {
redundantTypeArgs.add(typeArgumentList) redundantTypeArgs.add(typeArgumentList)
return return
} }
@@ -85,7 +117,7 @@ public class J2kPostProcessor(override val contextToAnalyzeIn: PsiElement, priva
super.visitPrefixExpression(expression) super.visitPrefixExpression(expression)
val intention = SimplifyNegatedBinaryExpressionIntention() val intention = SimplifyNegatedBinaryExpressionIntention()
if (intention.isApplicableTo(expression)) { if (rangeFilter(expression) == RangeFilterResult.PROCESS && intention.isApplicableTo(expression)) {
intention.applyTo(expression) intention.applyTo(expression)
} }
} }
@@ -93,19 +125,21 @@ public class J2kPostProcessor(override val contextToAnalyzeIn: PsiElement, priva
override fun visitIfExpression(expression: JetIfExpression) { override fun visitIfExpression(expression: JetIfExpression) {
super.visitIfExpression(expression) super.visitIfExpression(expression)
run { if (rangeFilter(expression) == RangeFilterResult.PROCESS) {
val intention = IfThenToSafeAccessIntention() run {
if (intention.isApplicableTo(expression)) { val intention = IfThenToSafeAccessIntention()
intention.applyTo(expression) if (intention.isApplicableTo(expression)) {
return intention.applyTo(expression)
return
}
} }
}
run { run {
val intention = IfThenToElvisIntention() val intention = IfThenToElvisIntention()
if (intention.isApplicableTo(expression)) { if (intention.isApplicableTo(expression)) {
intention.applyTo(expression) intention.applyTo(expression)
return return
}
} }
} }
} }
@@ -116,7 +150,15 @@ public class J2kPostProcessor(override val contextToAnalyzeIn: PsiElement, priva
} }
if (formatCode) { if (formatCode) {
CodeStyleManager.getInstance(contextToAnalyzeIn.getProject()).reformat(file) val codeStyleManager = CodeStyleManager.getInstance(file.getProject())
if (rangeMarker != null) {
if (rangeMarker.isValid()) {
codeStyleManager.reformatRange(file, rangeMarker.getStartOffset(), rangeMarker.getEndOffset())
}
}
else {
codeStyleManager.reformat(file)
}
} }
} }
} }
@@ -0,0 +1,6 @@
fun foo(o: Any) {
if (o is String) {
val l = o.length()
}
somethingElse()
}
@@ -0,0 +1,7 @@
class C {
void foo(Object o) {
<selection> if (o instanceof String) {
int l = ((String) o).length();
}
</selection> }
}
@@ -0,0 +1,3 @@
fun foo(o: Any) {
<caret> somethingElse()
}
@@ -0,0 +1,5 @@
fun foo(o: Any) {
if (o !is String) return
val l = o.length()
somethingElse(o as String/* we should not remove this cast because it's not in pasted range*/)
}
@@ -0,0 +1,6 @@
class C {
void foo(Object o) {
<selection> if (!(o instanceof String)) return
int l = ((String) o).length();
</selection> }
}
@@ -0,0 +1,3 @@
fun foo(o: Any) {
<caret> somethingElse(o as String/* we should not remove this cast because it's not in pasted range*/)
}
@@ -102,6 +102,18 @@ public class JavaToKotlinCopyPasteConversionTestGenerated extends AbstractJavaTo
doTest(fileName); doTest(fileName);
} }
@TestMetadata("RedundantTypeCast.java")
public void testRedundantTypeCast() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/copyPaste/conversion/RedundantTypeCast.java");
doTest(fileName);
}
@TestMetadata("RedundantTypeCast2.java")
public void testRedundantTypeCast2() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/copyPaste/conversion/RedundantTypeCast2.java");
doTest(fileName);
}
@TestMetadata("SampleBlock.java") @TestMetadata("SampleBlock.java")
public void testSampleBlock() throws Exception { public void testSampleBlock() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/copyPaste/conversion/SampleBlock.java"); String fileName = JetTestUtils.navigationMetadata("idea/testData/copyPaste/conversion/SampleBlock.java");
@@ -16,26 +16,33 @@
package org.jetbrains.kotlin.j2k package org.jetbrains.kotlin.j2k
import org.jetbrains.kotlin.psi.JetPsiFactory
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.psi.JetFile
import org.jetbrains.kotlin.psi.JetSimpleNameExpression
import org.jetbrains.kotlin.psi.JetUnaryExpression
import org.jetbrains.kotlin.psi.JetProperty
class AfterConversionPass(val project: Project, val postProcessor: PostProcessor) { public class AfterConversionPass(val project: Project, val postProcessor: PostProcessor) {
public fun run(kotlinCode: String): String { public fun run(kotlinFile: JetFile, range: TextRange?) {
//TODO: it's more correct to analyze all converted files together val bindingContext = postProcessor.analyzeFile(kotlinFile, range)
val kotlinFile = JetPsiFactory(project).createAnalyzableFile(
"fileForAfterConversionPass.kt", kotlinCode, postProcessor.contextToAnalyzeIn
)
val bindingContext = postProcessor.analyzeFile(kotlinFile)
val fixes = bindingContext.getDiagnostics().map { fun fixForProblem(diagnostic: Diagnostic): (() -> Unit)? {
val fix = postProcessor.fixForProblem(it) val psiElement = diagnostic.getPsiElement()
if (fix != null) it.getPsiElement() to fix else null if (range != null && psiElement.getTextRange() !in range) return null
}.filterNotNull() return postProcessor.fixForProblem(diagnostic)
}
val fixes = bindingContext.getDiagnostics()
.map {
val fix = fixForProblem(it)
if (fix != null) Pair(it.getPsiElement(), fix) else null
}
.filterNotNull()
val document = kotlinFile.getViewProvider().getDocument()!!
val rangeMarker = if (range != null) document.createRangeMarker(range.getStartOffset(), range.getEndOffset()) else null
rangeMarker?.setGreedyToLeft(true)
rangeMarker?.setGreedyToRight(true)
for ((psiElement, fix) in fixes) { for ((psiElement, fix) in fixes) {
if (psiElement.isValid()) { if (psiElement.isValid()) {
@@ -43,8 +50,6 @@ class AfterConversionPass(val project: Project, val postProcessor: PostProcessor
} }
} }
postProcessor.doAdditionalProcessing(kotlinFile) postProcessor.doAdditionalProcessing(kotlinFile, rangeMarker)
return kotlinFile.getText()!!
} }
} }
@@ -38,7 +38,6 @@ class Converter private(
val inConversionScope: (PsiElement) -> Boolean, val inConversionScope: (PsiElement) -> Boolean,
val referenceSearcher: ReferenceSearcher, val referenceSearcher: ReferenceSearcher,
val resolverForConverter: ResolverForConverter, val resolverForConverter: ResolverForConverter,
private val postProcessor: PostProcessor?,
private val commonState: Converter.CommonState, private val commonState: Converter.CommonState,
private val personalState: Converter.PersonalState private val personalState: Converter.PersonalState
) { ) {
@@ -60,16 +59,16 @@ class Converter private(
companion object { companion object {
public fun create(elementToConvert: PsiElement, settings: ConverterSettings, inConversionScope: (PsiElement) -> Boolean, public fun create(elementToConvert: PsiElement, settings: ConverterSettings, inConversionScope: (PsiElement) -> Boolean,
referenceSearcher: ReferenceSearcher, resolverForConverter: ResolverForConverter, postProcessor: PostProcessor?, referenceSearcher: ReferenceSearcher, resolverForConverter: ResolverForConverter,
usageProcessingsCollector: (UsageProcessing) -> Unit): Converter { usageProcessingsCollector: (UsageProcessing) -> Unit): Converter {
return Converter(elementToConvert, settings, inConversionScope, referenceSearcher, resolverForConverter, postProcessor, CommonState(usageProcessingsCollector), PersonalState(null)) return Converter(elementToConvert, settings, inConversionScope, referenceSearcher, resolverForConverter, CommonState(usageProcessingsCollector), PersonalState(null))
} }
} }
public fun withSpecialContext(context: PsiElement): Converter = withState(PersonalState(context)) public fun withSpecialContext(context: PsiElement): Converter = withState(PersonalState(context))
private fun withState(state: PersonalState): Converter private fun withState(state: PersonalState): Converter
= Converter(elementToConvert, settings, inConversionScope, referenceSearcher, resolverForConverter, postProcessor, commonState, state) = Converter(elementToConvert, settings, inConversionScope, referenceSearcher, resolverForConverter, commonState, state)
private fun createDefaultCodeConverter() = CodeConverter(this, DefaultExpressionConverter(), DefaultStatementConverter(), null) private fun createDefaultCodeConverter() = CodeConverter(this, DefaultExpressionConverter(), DefaultStatementConverter(), null)
@@ -17,14 +17,15 @@
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.editor.RangeMarker
import com.intellij.openapi.progress.EmptyProgressIndicator 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.ProgressIndicator
import com.intellij.openapi.progress.ProgressManager import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
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.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
import org.jetbrains.kotlin.j2k.ast.Element import org.jetbrains.kotlin.j2k.ast.Element
@@ -36,8 +37,7 @@ import java.util.ArrayList
import java.util.HashMap import java.util.HashMap
public trait PostProcessor { public trait PostProcessor {
public val contextToAnalyzeIn: PsiElement public fun analyzeFile(file: JetFile, range: TextRange?): BindingContext
public fun analyzeFile(file: JetFile): BindingContext
public open fun fixForProblem(problem: Diagnostic): (() -> Unit)? { public open fun fixForProblem(problem: Diagnostic): (() -> Unit)? {
val psiElement = problem.getPsiElement() val psiElement = problem.getPsiElement()
@@ -51,7 +51,7 @@ public trait PostProcessor {
Errors.VAL_REASSIGNMENT -> { -> Errors.VAL_REASSIGNMENT -> { ->
val property = (psiElement as? JetSimpleNameExpression)?.getReference()?.resolve() as? JetProperty val property = (psiElement as? JetSimpleNameExpression)?.getReference()?.resolve() as? JetProperty
if (property != null && !property.isVar()) { if (property != null && !property.isVar()) {
val factory = JetPsiFactory(contextToAnalyzeIn.getProject()) val factory = JetPsiFactory(psiElement.getProject())
property.getValOrVarNode().getPsi()!!.replace(factory.createVarNode().getPsi()!!) property.getValOrVarNode().getPsi()!!.replace(factory.createVarNode().getPsi()!!)
} }
} }
@@ -60,21 +60,27 @@ public trait PostProcessor {
} }
} }
public fun doAdditionalProcessing(file: JetFile) public fun doAdditionalProcessing(file: JetFile, rangeMarker: RangeMarker?)
} }
public class JavaToKotlinConverter(private val project: Project, public class JavaToKotlinConverter(private val project: Project,
private val settings: ConverterSettings, private val settings: ConverterSettings,
private val referenceSearcher: ReferenceSearcher, private val referenceSearcher: ReferenceSearcher,
private val resolverForConverter: ResolverForConverter) { private val resolverForConverter: ResolverForConverter,
private val postProcessor: PostProcessor?) {
private val LOG = Logger.getInstance("#org.jetbrains.kotlin.j2k.JavaToKotlinConverter") private val LOG = Logger.getInstance("#org.jetbrains.kotlin.j2k.JavaToKotlinConverter")
public data class InputElement(
val element: PsiElement,
val postProcessingContext: PsiElement?
)
public fun elementsToKotlin( public fun elementsToKotlin(
psiElementsAndProcessors: List<Pair<PsiElement, PostProcessor?>>, inputElements: List<InputElement>,
progress: ProgressIndicator = EmptyProgressIndicator() progress: ProgressIndicator = EmptyProgressIndicator()
): List<String> { ): List<String> {
try { try {
val elementCount = psiElementsAndProcessors.size() val elementCount = inputElements.size()
val intermediateResults = ArrayList<((Map<PsiElement, UsageProcessing>) -> String)?>(elementCount) 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 ->
@@ -94,12 +100,12 @@ public class JavaToKotlinConverter(private val project: Project,
{ {
progress.setText("$progressText ($fileCountText) - pass $pass of 3") progress.setText("$progressText ($fileCountText) - pass $pass of 3")
val filesCount = psiElementsAndProcessors.indices val filesCount = inputElements.indices
for (i in filesCount) { for (i in filesCount) {
progress.checkCanceled() progress.checkCanceled()
progress.setFraction(fraction + passFraction * i / elementCount) progress.setFraction(fraction + passFraction * i / elementCount)
val psiFile = psiElementsAndProcessors[i].first as? PsiFile val psiFile = inputElements[i].element as? PsiFile
if (psiFile != null) { if (psiFile != null) {
progress.setText2(psiFile.getVirtualFile().getPresentableUrl()) progress.setText2(psiFile.getVirtualFile().getPresentableUrl())
} }
@@ -114,13 +120,12 @@ public class JavaToKotlinConverter(private val project: Project,
} }
processFilesWithProgress(0.25) { i -> processFilesWithProgress(0.25) { i ->
val psiElement = psiElementsAndProcessors[i].first val psiElement = inputElements[i].element
val postProcessor = psiElementsAndProcessors[i].second
fun inConversionScope(element: PsiElement) fun inConversionScope(element: PsiElement)
= psiElementsAndProcessors.any { it.first.isAncestor(element, strict = false) } = inputElements.any { it.element.isAncestor(element, strict = false) }
val converter = Converter.create(psiElement, settings, ::inConversionScope, referenceSearcher, resolverForConverter, postProcessor, usageProcessingCollector) val converter = Converter.create(psiElement, settings, ::inConversionScope, referenceSearcher, resolverForConverter, usageProcessingCollector)
val result = converter.convert() val result = converter.convert()
intermediateResults.add(result) intermediateResults.add(result)
} }
@@ -135,10 +140,11 @@ public class JavaToKotlinConverter(private val project: Project,
val finalResults = ArrayList<String>(elementCount) val finalResults = ArrayList<String>(elementCount)
processFilesWithProgress(0.5) { i -> processFilesWithProgress(0.5) { i ->
val result = results[i] val result = results[i]
val postProcessor = psiElementsAndProcessors[i].second
if (postProcessor != null) { if (postProcessor != null) {
try { try {
finalResults.add(AfterConversionPass(project, postProcessor).run(result)) val kotlinFile = JetPsiFactory(project).createAnalyzableFile("dummy.kt", result, inputElements[i].postProcessingContext!!)
AfterConversionPass(project, postProcessor).run(kotlinFile, null)
finalResults.add(kotlinFile.getText())
} }
catch(e: ProcessCanceledException) { catch(e: ProcessCanceledException) {
throw e throw e
@@ -159,7 +165,7 @@ public class JavaToKotlinConverter(private val project: Project,
// if we got this exception then we need to turn element creation stack traces on to get better diagnostic // if we got this exception then we need to turn element creation stack traces on to get better diagnostic
Element.saveCreationStacktraces = true Element.saveCreationStacktraces = true
try { try {
return elementsToKotlin(psiElementsAndProcessors) return elementsToKotlin(inputElements)
} }
finally { finally {
Element.saveCreationStacktraces = false Element.saveCreationStacktraces = false
@@ -134,8 +134,8 @@ public object JavaToKotlinTranslator {
fun generateKotlinCode(javaCode: String): String { fun generateKotlinCode(javaCode: String): String {
val file = createFile(javaCode) val file = createFile(javaCode)
if (file is PsiJavaFile) { if (file is PsiJavaFile) {
val converter = JavaToKotlinConverter(file.getProject(), ConverterSettings.defaultSettings, EmptyReferenceSearcher, EmptyResolverForConverter) val converter = JavaToKotlinConverter(file.getProject(), ConverterSettings.defaultSettings, EmptyReferenceSearcher, EmptyResolverForConverter, null)
return prettify(converter.elementsToKotlin(listOf(file to null))[0]) return prettify(converter.elementsToKotlin(listOf(JavaToKotlinConverter.InputElement(file, null))).single())
} }
return "" return ""
} }
@@ -43,8 +43,10 @@ public abstract class AbstractJavaToKotlinConverterMultiFileTest : AbstractJavaT
psiFiles.add(psiFile) psiFiles.add(psiFile)
} }
val converter = JavaToKotlinConverter(project, ConverterSettings.defaultSettings, IdeaReferenceSearcher, IdeaResolverForConverter) val converter = JavaToKotlinConverter(project, ConverterSettings.defaultSettings,
val results: List<String> = converter.elementsToKotlin(psiFiles.map { it to J2kPostProcessor(it, formatCode = true) }) IdeaReferenceSearcher, IdeaResolverForConverter, J2kPostProcessor(formatCode = true))
val inputElements = psiFiles.map { JavaToKotlinConverter.InputElement(it, it) }
val results: List<String> = converter.elementsToKotlin(inputElements)
fun expectedFile(i: Int) = File(javaFiles[i].getPath().replace(".java", ".kt")) fun expectedFile(i: Int) = File(javaFiles[i].getPath().replace(".java", ".kt"))
@@ -106,8 +106,10 @@ public abstract class AbstractJavaToKotlinConverterSingleFileTest : AbstractJava
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, IdeaReferenceSearcher, IdeaResolverForConverter) val converter = JavaToKotlinConverter(project, settings,
return converter.elementsToKotlin(listOf(file to J2kPostProcessor(file, formatCode = false)))[0] IdeaReferenceSearcher, IdeaResolverForConverter, J2kPostProcessor(formatCode = false))
val inputElements = listOf(JavaToKotlinConverter.InputElement(file, file))
return converter.elementsToKotlin(inputElements).single()
} }
private fun methodToKotlin(text: String, settings: ConverterSettings, project: Project): String { private fun methodToKotlin(text: String, settings: ConverterSettings, project: Project): String {