KT-5959 Imports should be inserted on paste from java with conversion to Kotlin

#KT-5959 Fixed
This commit is contained in:
Valentin Kipyatkov
2015-04-03 15:55:56 +03:00
parent 754567af07
commit 506f84fb8a
41 changed files with 462 additions and 172 deletions
@@ -54,6 +54,7 @@ public class JavaToKotlinAction : AnAction() {
IdeaReferenceSearcher, IdeaResolverForConverter, J2kPostProcessor(formatCode = true)) IdeaReferenceSearcher, IdeaResolverForConverter, J2kPostProcessor(formatCode = true))
val inputElements = javaFiles.map { JavaToKotlinConverter.InputElement(it, it) } val inputElements = javaFiles.map { JavaToKotlinConverter.InputElement(it, it) }
convertedTexts = converter.elementsToKotlin(inputElements, ProgressManager.getInstance().getProgressIndicator()) convertedTexts = converter.elementsToKotlin(inputElements, ProgressManager.getInstance().getProgressIndicator())
.map { it!!.text /*conversion of a file always succeeds*/ }
} }
if (!ProgressManager.getInstance().runProcessWithProgressSynchronously( if (!ProgressManager.getInstance().runProcessWithProgressSynchronously(
@@ -85,11 +85,7 @@ public class KotlinCopyPasteReferenceProcessor() : CopyPastePostProcessor<Kotlin
if (file !is JetFile || DumbService.getInstance(file.getProject()).isDumb()) return listOf() if (file !is JetFile || DumbService.getInstance(file.getProject()).isDumb()) return listOf()
val collectedData = try { val collectedData = try {
toTextRanges(startOffsets, endOffsets).flatMap { collectReferenceData(file, startOffsets, endOffsets)
CollectHighlightsUtil.getElementsInRange(file, it.start, it.end).flatMap { element ->
collectReferenceDataFromElement(element, file, it.start, startOffsets, endOffsets)
}
}
} }
catch (e: ProcessCanceledException) { catch (e: ProcessCanceledException) {
// supposedly analysis can only be canceled from another thread // supposedly analysis can only be canceled from another thread
@@ -107,23 +103,36 @@ public class KotlinCopyPasteReferenceProcessor() : CopyPastePostProcessor<Kotlin
return listOf(KotlinReferenceTransferableData(collectedData.copyToArray())) return listOf(KotlinReferenceTransferableData(collectedData.copyToArray()))
} }
private fun collectReferenceDataFromElement( public fun collectReferenceData(
file: JetFile,
startOffsets: IntArray,
endOffsets: IntArray
): List<KotlinReferenceData> {
val result = ArrayList<KotlinReferenceData>()
for (range in toTextRanges(startOffsets, endOffsets)) {
for (element in CollectHighlightsUtil.getElementsInRange(file, range.start, range.end)) {
result.addReferenceDataFromElement(element, file, range.start, startOffsets, endOffsets)
}
}
return result
}
private fun MutableCollection<KotlinReferenceData>.addReferenceDataFromElement(
element: PsiElement, element: PsiElement,
file: JetFile, file: JetFile,
startOffset: Int, startOffset: Int,
startOffsets: IntArray, startOffsets: IntArray,
endOffsets: IntArray endOffsets: IntArray
): Collection<KotlinReferenceData> { ) {
if (PsiTreeUtil.getParentOfType(element, *IGNORE_REFERENCES_INSIDE) != null) return listOf() if (PsiTreeUtil.getParentOfType(element, *IGNORE_REFERENCES_INSIDE) != null) return
val reference = element.getReference() as? JetReference ?: return listOf() val reference = element.getReference() as? JetReference ?: return
val descriptors = reference.resolveToDescriptors((element as JetElement).analyze()) //TODO: we could use partial body resolve for all references together val descriptors = reference.resolveToDescriptors((element as JetElement).analyze()) //TODO: we could use partial body resolve for all references together
//check whether this reference is unambiguous //check whether this reference is unambiguous
if (reference !is JetMultiReference<*> && descriptors.size() > 1) return listOf() if (reference !is JetMultiReference<*> && descriptors.size() > 1) return
val collectedData = ArrayList<KotlinReferenceData>()
for (descriptor in descriptors) { for (descriptor in descriptors) {
val declarations = DescriptorToSourceUtilsIde.getAllDeclarations(file.getProject(), descriptor) val declarations = DescriptorToSourceUtilsIde.getAllDeclarations(file.getProject(), descriptor)
val declaration = declarations.singleOrNull() val declaration = declarations.singleOrNull()
@@ -139,9 +148,8 @@ public class KotlinCopyPasteReferenceProcessor() : CopyPastePostProcessor<Kotlin
if (!descriptor.canBeReferencedViaImport()) continue if (!descriptor.canBeReferencedViaImport()) continue
val kind = referenceDataKind(descriptor) ?: continue val kind = referenceDataKind(descriptor) ?: continue
collectedData.add(KotlinReferenceData(element.range.start - startOffset, element.range.end - startOffset, fqName.asString(), kind)) add(KotlinReferenceData(element.range.start - startOffset, element.range.end - startOffset, fqName.asString(), kind))
} }
return collectedData
} }
private fun referenceDataKind(descriptor: DeclarationDescriptor): KotlinReferenceData.Kind? { private fun referenceDataKind(descriptor: DeclarationDescriptor): KotlinReferenceData.Kind? {
@@ -179,12 +187,16 @@ public class KotlinCopyPasteReferenceProcessor() : CopyPastePostProcessor<Kotlin
val file = PsiDocumentManager.getInstance(project).getPsiFile(document) val file = PsiDocumentManager.getInstance(project).getPsiFile(document)
if (file !is JetFile) return if (file !is JetFile) return
assert(values.size() == 1)
val referenceData = values.single().data
processReferenceData(project, file, bounds.getStartOffset(), referenceData)
}
public fun processReferenceData(project: Project, file: JetFile, boundStart: Int, referenceData: Array<KotlinReferenceData>) {
PsiDocumentManager.getInstance(project).commitAllDocuments() PsiDocumentManager.getInstance(project).commitAllDocuments()
assert(values.size() == 1) val referencesPossibleToRestore = findReferencesToRestore(file, boundStart, referenceData)
val referenceData = values.single().data
val referencesPossibleToRestore = findReferencesToRestore(file, bounds, referenceData)
val selectedReferencesToRestore = showRestoreReferencesDialog(project, referencesPossibleToRestore) val selectedReferencesToRestore = showRestoreReferencesDialog(project, referencesPossibleToRestore)
if (selectedReferencesToRestore.isEmpty()) return if (selectedReferencesToRestore.isEmpty()) return
@@ -192,14 +204,13 @@ public class KotlinCopyPasteReferenceProcessor() : CopyPastePostProcessor<Kotlin
ApplicationManager.getApplication()!!.runWriteAction(Runnable { ApplicationManager.getApplication()!!.runWriteAction(Runnable {
restoreReferences(selectedReferencesToRestore, file) restoreReferences(selectedReferencesToRestore, file)
}) })
PsiDocumentManager.getInstance(project).commitAllDocuments()
} }
private fun findReferencesToRestore(file: PsiFile, bounds: RangeMarker, referenceData: Array<out KotlinReferenceData>): List<ReferenceToRestoreData> { private fun findReferencesToRestore(file: PsiFile, boundStart: Int, referenceData: Array<out KotlinReferenceData>): List<ReferenceToRestoreData> {
if (file !is JetFile) return listOf() if (file !is JetFile) return listOf()
return referenceData.map { return referenceData.map {
val referenceElement = findReference(it, file, bounds) val referenceElement = findReference(it, file, boundStart)
if (referenceElement != null) if (referenceElement != null)
createReferenceToRestoreData(referenceElement, it) createReferenceToRestoreData(referenceElement, it)
else else
@@ -207,9 +218,9 @@ public class KotlinCopyPasteReferenceProcessor() : CopyPastePostProcessor<Kotlin
}.filterNotNull() }.filterNotNull()
} }
private fun findReference(data: KotlinReferenceData, file: JetFile, bounds: RangeMarker): JetElement? { private fun findReference(data: KotlinReferenceData, file: JetFile, boundStart: Int): JetElement? {
val startOffset = data.startOffset + bounds.getStartOffset() val startOffset = data.startOffset + boundStart
val endOffset = data.endOffset + bounds.getStartOffset() val endOffset = data.endOffset + boundStart
val element = file.findElementAt(startOffset) val element = file.findElementAt(startOffset)
val desiredRange = TextRange(startOffset, endOffset) val desiredRange = TextRange(startOffset, endOffset)
var expression = element var expression = element
@@ -28,17 +28,18 @@ 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.psi.* import com.intellij.psi.*
import com.intellij.psi.codeStyle.CodeStyleManager import org.jetbrains.kotlin.idea.codeInsight.KotlinCopyPasteReferenceProcessor
import org.jetbrains.kotlin.idea.codeInsight.KotlinReferenceData
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.*
import org.jetbrains.kotlin.j2k.ConverterSettings import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.j2k.IdeaReferenceSearcher import org.jetbrains.kotlin.name.FqNameUnsafe
import org.jetbrains.kotlin.j2k.JavaToKotlinConverter import org.jetbrains.kotlin.platform.JavaToKotlinClassMap
import org.jetbrains.kotlin.psi.JetFile import org.jetbrains.kotlin.psi.JetFile
import org.jetbrains.kotlin.psi.JetPsiFactory
import org.jetbrains.kotlin.psi.psiUtil.elementsInRange import org.jetbrains.kotlin.psi.psiUtil.elementsInRange
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
@@ -69,43 +70,80 @@ public class ConvertJavaCopyPastePostProcessor : CopyPastePostProcessor<TextBloc
val data = values.single() val data = values.single()
if (data !is CopiedCode) return if (data !is CopiedCode) return
val targetFile = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument()) as? JetFile ?: return val document = editor.getDocument()
val targetFile = PsiDocumentManager.getInstance(project).getPsiFile(document) as? JetFile ?: return
fun doConversion(): String? { fun doConversion(): Pair<String?, Collection<KotlinReferenceData>> {
//TODO: create it in correct context!
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) val result = convertCopiedCodeToKotlin(data, sourceFile)
val referenceData = buildReferenceData(result.text, result.parseContext, sourceFile, targetFile)
return (if (result.textChanged) result.text else null) to referenceData
} }
var conversionResult: String? = null fun insertImports(bounds: TextRange, referenceData: Collection<KotlinReferenceData>): TextRange? {
if (referenceData.isEmpty()) return bounds
val rangeMarker = document.createRangeMarker(bounds)
rangeMarker.setGreedyToLeft(true)
rangeMarker.setGreedyToRight(true)
KotlinCopyPasteReferenceProcessor().processReferenceData(project, targetFile, bounds.start, referenceData.copyToArray())
return rangeMarker.range
}
var conversionResult: Pair<String?, Collection<KotlinReferenceData>>? = null
fun doConversionAndInsertImportsIfNoConversion(): Boolean {
conversionResult = doConversion()
val text = conversionResult!!.first
if (text != null) return false
insertImports(bounds.range ?: return true, conversionResult!!.second)
return true
}
val textLength = data.startOffsets.indices.sumBy { data.endOffsets[it] - data.startOffsets[it] } val textLength = data.startOffsets.indices.sumBy { data.endOffsets[it] - data.startOffsets[it] }
if (textLength < 1000) { // if the text to convert is short enough, try to do conversion without permission from user and skip the dialog if nothing converted if (textLength < 1000) { // if the text to convert is short enough, try to do conversion without permission from user and skip the dialog if nothing converted
conversionResult = doConversion() ?: return if (doConversionAndInsertImportsIfNoConversion()) return
} }
val jetEditorOptions = JetEditorOptions.getInstance()!! val jetEditorOptions = JetEditorOptions.getInstance()!!
val needConvert = jetEditorOptions.isEnableJavaToKotlinConversion() && (jetEditorOptions.isDonTShowConversionDialog() || okFromDialog(project)) val needConvert = jetEditorOptions.isEnableJavaToKotlinConversion() && (jetEditorOptions.isDonTShowConversionDialog() || okFromDialog(project))
if (needConvert) { if (needConvert) {
if (conversionResult == null) { if (conversionResult == null) {
conversionResult = doConversion() ?: return if (doConversionAndInsertImportsIfNoConversion()) return
} }
val (text, referenceData) = conversionResult!!
text!! // otherwise we should get true from doConversionAndInsertImportsIfNoConversion and return above
ApplicationManager.getApplication()!!.runWriteAction { ApplicationManager.getApplication()!!.runWriteAction {
val startOffset = bounds.getStartOffset() val startOffset = bounds.getStartOffset()
editor.getDocument().replaceString(startOffset, bounds.getEndOffset(), conversionResult!!) document.replaceString(startOffset, bounds.getEndOffset(), text)
val endOffsetAfterCopy = startOffset + conversionResult!!.length() val endOffsetAfterCopy = startOffset + text.length()
editor.getCaretModel().moveToOffset(endOffsetAfterCopy) editor.getCaretModel().moveToOffset(endOffsetAfterCopy)
var newBounds = insertImports(TextRange(startOffset, endOffsetAfterCopy), referenceData)
PsiDocumentManager.getInstance(project).commitAllDocuments() PsiDocumentManager.getInstance(project).commitAllDocuments()
AfterConversionPass(project, J2kPostProcessor(formatCode = true)).run(targetFile, TextRange(startOffset, endOffsetAfterCopy)) AfterConversionPass(project, J2kPostProcessor(formatCode = true)).run(targetFile, newBounds)
conversionPerformed = true conversionPerformed = true
} }
} }
} }
private fun convertCopiedCodeToKotlin(code: CopiedCode, sourceFile: PsiJavaFile): String? { private data class ConversionResult(
val text: String,
val parseContext: ParseContext,
val textChanged: Boolean
)
private fun convertCopiedCodeToKotlin(code: CopiedCode, sourceFile: PsiJavaFile): ConversionResult {
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
@@ -114,8 +152,6 @@ public class ConvertJavaCopyPastePostProcessor : CopyPastePostProcessor<TextBloc
list.collectElementsToConvert(sourceFile, sourceFileText, TextRange(code.startOffsets[i], code.endOffsets[i])) list.collectElementsToConvert(sourceFile, sourceFileText, TextRange(code.startOffsets[i], code.endOffsets[i]))
} }
if (list.all { it is String }) return null // nothing to convert
val converter = JavaToKotlinConverter( val converter = JavaToKotlinConverter(
sourceFile.getProject(), sourceFile.getProject(),
ConverterSettings.defaultSettings, ConverterSettings.defaultSettings,
@@ -130,12 +166,16 @@ public class ConvertJavaCopyPastePostProcessor : CopyPastePostProcessor<TextBloc
var resultIndex = 0 var resultIndex = 0
val convertedCodeBuilder = StringBuilder() val convertedCodeBuilder = StringBuilder()
val originalCodeBuilder = StringBuilder() val originalCodeBuilder = StringBuilder()
var parseContext: ParseContext? = null
for (o in list) { for (o in list) {
if (o is PsiElement) { if (o is PsiElement) {
val originalText = o.getText() val originalText = o.getText()
val result = results[resultIndex++] val result = results[resultIndex++]
if (!result.isEmpty()) { if (result != null) {
convertedCodeBuilder.append(result) convertedCodeBuilder.append(result.text)
if (parseContext == null) { // use parse context of the first converted element as parse context for the whole text
parseContext = result.parseContext
}
} }
else { // failed to convert element to Kotlin, insert "as is" else { // failed to convert element to Kotlin, insert "as is"
convertedCodeBuilder.append(originalText) convertedCodeBuilder.append(originalText)
@@ -150,9 +190,7 @@ public class ConvertJavaCopyPastePostProcessor : CopyPastePostProcessor<TextBloc
val convertedCode = convertedCodeBuilder.toString() val convertedCode = convertedCodeBuilder.toString()
val originalCode = originalCodeBuilder.toString() val originalCode = originalCodeBuilder.toString()
if (convertedCode == originalCode) return null return ConversionResult(convertedCode, parseContext ?: ParseContext.TOP_LEVEL, convertedCode != originalCode)
return convertedCode
} }
// builds list consisting of PsiElement's to convert and plain String's // builds list consisting of PsiElement's to convert and plain String's
@@ -172,6 +210,76 @@ public class ConvertJavaCopyPastePostProcessor : CopyPastePostProcessor<TextBloc
} }
} }
private fun buildReferenceData(text: String, parseContext: ParseContext, sourceFile: PsiJavaFile, targetFile: JetFile): Collection<KotlinReferenceData> {
var blockStart: Int? = null
var blockEnd: Int? = null
val fileText = StringBuilder {
val packageName = sourceFile.getPackageName()
if (!packageName.isEmpty()) {
append("package ").append(packageName).append("\n")
}
val importList = sourceFile.getImportList()
if (importList != null) {
for (import in importList.getImportStatements()) {
val qualifiedName = import.getQualifiedName() ?: continue
if (import.isOnDemand()) {
append("import ").append(qualifiedName).append(".*\n")
}
else {
val fqName = FqNameUnsafe(qualifiedName)
// skip explicit imports of platform classes mapped into Kotlin classes
if (fqName.isSafe() && JavaToKotlinClassMap.INSTANCE.mapPlatformClass(fqName.toSafe()).isNotEmpty()) continue
append("import ").append(qualifiedName).append("\n")
}
}
//TODO: static imports
}
val contextPrefix: String
val contextSuffix: String
when (parseContext) {
ParseContext.CODE_BLOCK -> {
contextPrefix = "fun ${generateDummyFunctionName(text)}() {\n"
contextSuffix = "\n}"
}
ParseContext.TOP_LEVEL -> {
contextPrefix = ""
contextSuffix = ""
}
//TODO: see KT-7264
else -> {
contextPrefix = ""
contextSuffix = ""
}
}
append(contextPrefix)
blockStart = length()
append(text)
blockEnd = length()
append(contextSuffix)
}.toString()
val dummyFile = JetPsiFactory(targetFile.getProject()).createAnalyzableFile("dummy.kt", fileText, targetFile)
return KotlinCopyPasteReferenceProcessor().collectReferenceData(dummyFile, intArray(blockStart!!), intArray(blockEnd!!))
}
private fun generateDummyFunctionName(convertedCode: String): String {
var i = 0
while (true) {
val name = "dummy$i"
if (convertedCode.indexOf(name) < 0) return name
i++
}
}
private fun okFromDialog(project: Project): Boolean { private fun okFromDialog(project: Project): Boolean {
val dialog = KotlinPasteFromJavaDialog(project) val dialog = KotlinPasteFromJavaDialog(project)
dialog.show() dialog.show()
@@ -16,6 +16,7 @@
package org.jetbrains.kotlin.idea.conversion.copy package org.jetbrains.kotlin.idea.conversion.copy
import com.intellij.openapi.editor.RangeMarker
import com.intellij.openapi.util.TextRange import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
@@ -29,3 +30,6 @@ public val TextRange.end: Int
public val PsiElement.range: TextRange public val PsiElement.range: TextRange
get() = getTextRange()!! get() = getTextRange()!!
public val RangeMarker.range: TextRange?
get() = if (isValid()) TextRange(getStartOffset(), getEndOffset()) else null
@@ -0,0 +1,5 @@
import java.io.File
fun foo(file: File): List<String> {
return emptyList()
}
@@ -0,0 +1,9 @@
import java.io.File;
import java.util.Collections;
import java.util.List;
class C {
<selection> List<String> foo(File file) {
return Collections.emptyList();
}
</selection>}
@@ -0,0 +1 @@
<caret>
@@ -0,0 +1,3 @@
import java.io.File
fun foo(): List<File><caret>
@@ -0,0 +1,10 @@
// NO_CONVERSION_EXPECTED
import java.io.*;
import java.util.Collections;
import java.util.List;
class C {
<selection>List<File></selection> foo() {
return Collections.emptyList();
}
}
@@ -0,0 +1 @@
fun foo(): <caret>
@@ -0,0 +1,5 @@
import java.io.File
fun foo() {
list.add(File.separatorChar)<caret>
}
@@ -0,0 +1,9 @@
// NO_CONVERSION_EXPECTED
import java.io.*;
import java.util.*;
class C {
void foo(List<Character> list) {
<selection>list.add(File.separatorChar)</selection>
}
}
@@ -0,0 +1,3 @@
fun foo() {
<caret>
}
@@ -0,0 +1,8 @@
package ppp
import kotlin.platform.platformStatic
public object Dependency {
[platformStatic]
public val FIELD: Int = 1
}
@@ -0,0 +1,5 @@
import ppp.Dependency
fun f() {
return Dependency.FIELD<caret>
}
@@ -0,0 +1,7 @@
package ppp
class C {
char foo() {
<selection>return Dependency.FIELD;</selection>
}
}
@@ -0,0 +1,3 @@
fun f() {
<caret>
}
@@ -0,0 +1,3 @@
package ppp
class dummy0
@@ -0,0 +1,5 @@
import ppp.dummy0
fun f() {
return dummy0()<caret>
}
@@ -0,0 +1,7 @@
import ppp.*
class C {
dummy0 foo() {
<selection>return dummy0();</selection>
}
}
@@ -0,0 +1,3 @@
fun f() {
<caret>
}
@@ -1 +1 @@
No conversion performed val v = b - c + d
@@ -1,3 +1,4 @@
// NO_CONVERSION_EXPECTED
public static void main(String[]args){ public static void main(String[]args){
a + <selection>b - c + d</selection> - e a + <selection>b - c + d</selection> - e
} }
@@ -1 +1,4 @@
No conversion performed fun main(args: Array<String>) {
reference(arg)
somethingElse()
}
@@ -1,3 +1,4 @@
// NO_CONVERSION_EXPECTED
class A { class A {
public static void main(String[] args) { public static void main(String[] args) {
qualifier.<selection>reference(arg)</selection>; qualifier.<selection>reference(arg)</selection>;
@@ -1 +1 @@
No conversion performed }
@@ -1,3 +1,4 @@
// NO_CONVERSION_EXPECTED
class A { class A {
public static void main(String[] args) { public static void main(String[] args) {
//sdasd //sdasd
@@ -1 +1,4 @@
No conversion performed fun main(args: Array<String>) {
qualifier
somethingElse()
}
@@ -1,3 +1,4 @@
// NO_CONVERSION_EXPECTED
class A { class A {
public static void main(String[] args) { public static void main(String[] args) {
<selection>qualifier</selection>.reference(arg); <selection>qualifier</selection>.reference(arg);
@@ -1 +1 @@
No conversion performed class Some
@@ -1,3 +1,4 @@
// NO_CONVERSION_EXPECTED
<selection>class Some</selection>InterestingClass { <selection>class Some</selection>InterestingClass {
} }
@@ -0,0 +1,35 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea
import com.intellij.codeInsight.CodeInsightSettings
public abstract class AbstractCopyPasteTest : JetLightCodeInsightFixtureTestCase() {
private var savedImportsOnPasteSetting: Int = 0
override fun setUp() {
super.setUp()
savedImportsOnPasteSetting = CodeInsightSettings.getInstance().ADD_IMPORTS_ON_PASTE
CodeInsightSettings.getInstance().ADD_IMPORTS_ON_PASTE = CodeInsightSettings.YES
}
override fun tearDown() {
CodeInsightSettings.getInstance().ADD_IMPORTS_ON_PASTE = savedImportsOnPasteSetting
super.tearDown()
}
}
@@ -16,7 +16,6 @@
package org.jetbrains.kotlin.idea.codeInsight; package org.jetbrains.kotlin.idea.codeInsight;
import com.intellij.codeInsight.CodeInsightSettings;
import com.intellij.openapi.actionSystem.IdeActions; import com.intellij.openapi.actionSystem.IdeActions;
import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.io.FileUtil;
@@ -26,7 +25,7 @@ import org.jetbrains.kotlin.diagnostics.Diagnostic;
import org.jetbrains.kotlin.diagnostics.DiagnosticUtils; import org.jetbrains.kotlin.diagnostics.DiagnosticUtils;
import org.jetbrains.kotlin.diagnostics.Errors; import org.jetbrains.kotlin.diagnostics.Errors;
import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages; import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages;
import org.jetbrains.kotlin.idea.JetLightCodeInsightFixtureTestCase; import org.jetbrains.kotlin.idea.AbstractCopyPasteTest;
import org.jetbrains.kotlin.idea.PluginTestCaseBase; import org.jetbrains.kotlin.idea.PluginTestCaseBase;
import org.jetbrains.kotlin.idea.caches.resolve.ResolvePackage; import org.jetbrains.kotlin.idea.caches.resolve.ResolvePackage;
import org.jetbrains.kotlin.psi.JetFile; import org.jetbrains.kotlin.psi.JetFile;
@@ -38,26 +37,11 @@ import org.jetbrains.kotlin.test.InTextDirectivesUtils;
import java.io.File; import java.io.File;
import java.util.List; import java.util.List;
public abstract class AbstractInsertImportOnPasteTest extends JetLightCodeInsightFixtureTestCase { public abstract class AbstractInsertImportOnPasteTest extends AbstractCopyPasteTest {
private static final String BASE_PATH = PluginTestCaseBase.getTestDataPathBase() + "/copyPaste/imports"; private static final String BASE_PATH = PluginTestCaseBase.getTestDataPathBase() + "/copyPaste/imports";
private static final String DEFAULT_TO_FILE_TEXT = "package to\n\n<caret>"; private static final String DEFAULT_TO_FILE_TEXT = "package to\n\n<caret>";
private static final String ALLOW_UNRESOLVED_DIRECTIVE = "// ALLOW_UNRESOLVED"; private static final String ALLOW_UNRESOLVED_DIRECTIVE = "// ALLOW_UNRESOLVED";
private int savedState;
@Override
protected void setUp() throws Exception {
super.setUp();
savedState = CodeInsightSettings.getInstance().ADD_IMPORTS_ON_PASTE;
CodeInsightSettings.getInstance().ADD_IMPORTS_ON_PASTE = CodeInsightSettings.YES;
}
@Override
protected void tearDown() throws Exception {
CodeInsightSettings.getInstance().ADD_IMPORTS_ON_PASTE = savedState;
super.tearDown();
}
public void doTestCut(String path) throws Exception { public void doTestCut(String path) throws Exception {
doTestAction(IdeActions.ACTION_CUT, path); doTestAction(IdeActions.ACTION_CUT, path);
} }
@@ -1,84 +0,0 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.conversion.copy;
import com.intellij.openapi.actionSystem.IdeActions;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.editor.Document;
import com.intellij.testFramework.LightProjectDescriptor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.idea.JetLightCodeInsightFixtureTestCase;
import org.jetbrains.kotlin.idea.JetWithJdkAndRuntimeLightProjectDescriptor;
import org.jetbrains.kotlin.idea.PluginTestCaseBase;
import org.jetbrains.kotlin.idea.editor.JetEditorOptions;
public abstract class AbstractJavaToKotlinCopyPasteConversionTest extends JetLightCodeInsightFixtureTestCase {
private static final String BASE_PATH = PluginTestCaseBase.getTestDataPathBase() + "/copyPaste/conversion";
private JetEditorOptions oldState = null;
@NotNull
@Override
protected LightProjectDescriptor getProjectDescriptor() {
return JetWithJdkAndRuntimeLightProjectDescriptor.INSTANCE;
}
@Override
protected void setUp() throws Exception {
super.setUp();
oldState = JetEditorOptions.getInstance().getState();
JetEditorOptions.getInstance().setEnableJavaToKotlinConversion(true);
JetEditorOptions.getInstance().setDonTShowConversionDialog(true);
}
@Override
public void tearDown() throws Exception {
JetEditorOptions.getInstance().loadState(oldState);
super.tearDown();
}
public void doTest(@SuppressWarnings("UnusedParameters") String path) throws Exception {
myFixture.setTestDataPath(BASE_PATH);
String testName = getTestName(false);
myFixture.configureByFile(testName + ".java");
myFixture.performEditorAction(IdeActions.ACTION_COPY);
//noinspection AssignmentToStaticFieldFromInstanceMethod
ConvertJavaCopyPastePostProcessor.conversionPerformed = false;
myFixture.configureByFile(testName + ".to.kt");
myFixture.performEditorAction(IdeActions.ACTION_PASTE);
if (!ConvertJavaCopyPastePostProcessor.conversionPerformed) {
CommandProcessor.getInstance().executeCommand(getProject(), new Runnable() {
@Override
public void run() {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
Document document = myFixture.getEditor().getDocument();
document.replaceString(0, document.getTextLength(), "No conversion performed");
}
});
}
}, "", null);
}
myFixture.checkResultByFile(testName + ".expected.kt");
}
}
@@ -0,0 +1,79 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.conversion.copy
import com.intellij.openapi.actionSystem.IdeActions
import com.intellij.testFramework.LightProjectDescriptor
import org.jetbrains.kotlin.idea.AbstractCopyPasteTest
import org.jetbrains.kotlin.idea.JetWithJdkAndRuntimeLightProjectDescriptor
import org.jetbrains.kotlin.idea.PluginTestCaseBase
import org.jetbrains.kotlin.idea.editor.JetEditorOptions
import org.jetbrains.kotlin.test.InTextDirectivesUtils
import java.io.File
import kotlin.test.assertEquals
public abstract class AbstractJavaToKotlinCopyPasteConversionTest : AbstractCopyPasteTest() {
private val BASE_PATH = PluginTestCaseBase.getTestDataPathBase() + "/copyPaste/conversion"
private var oldEditorOptions: JetEditorOptions? = null
override fun getProjectDescriptor(): LightProjectDescriptor {
return JetWithJdkAndRuntimeLightProjectDescriptor.INSTANCE
}
override fun setUp() {
super.setUp()
oldEditorOptions = JetEditorOptions.getInstance().getState()
JetEditorOptions.getInstance().setEnableJavaToKotlinConversion(true)
JetEditorOptions.getInstance().setDonTShowConversionDialog(true)
}
override fun tearDown() {
JetEditorOptions.getInstance().loadState(oldEditorOptions)
super.tearDown()
}
public fun doTest(path: String) {
myFixture.setTestDataPath(BASE_PATH)
val testName = getTestName(false)
myFixture.configureByFiles(testName + ".java")
val fileText = myFixture.getEditor().getDocument().getText()
val noConversionExpected = InTextDirectivesUtils.findListWithPrefixes(fileText, "// NO_CONVERSION_EXPECTED").isNotEmpty()
myFixture.performEditorAction(IdeActions.ACTION_COPY)
val toFileName = testName + ".to.kt"
val dependencyFileName = testName + ".dependency.kt"
if (File(BASE_PATH + File.separator + dependencyFileName).exists()) {
myFixture.configureByFiles(toFileName, dependencyFileName)
}
else {
myFixture.configureByFile(toFileName)
}
ConvertJavaCopyPastePostProcessor.conversionPerformed = false
myFixture.performEditorAction(IdeActions.ACTION_PASTE)
assertEquals(noConversionExpected, !ConvertJavaCopyPastePostProcessor.conversionPerformed,
if (noConversionExpected) "Conversion to Kotlin should not be suggested" else "No conversion to Kotlin suggested")
myFixture.checkResultByFile(testName + ".expected.kt")
}
}
@@ -32,6 +32,36 @@ import java.util.regex.Pattern;
@TestDataPath("$PROJECT_ROOT") @TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class) @RunWith(JUnit3RunnerWithInners.class)
public class JavaToKotlinCopyPasteConversionTestGenerated extends AbstractJavaToKotlinCopyPasteConversionTest { public class JavaToKotlinCopyPasteConversionTestGenerated extends AbstractJavaToKotlinCopyPasteConversionTest {
@TestMetadata("AddImports.java")
public void testAddImports() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/copyPaste/conversion/AddImports.java");
doTest(fileName);
}
@TestMetadata("AddImportsButNoConversion.java")
public void testAddImportsButNoConversion() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/copyPaste/conversion/AddImportsButNoConversion.java");
doTest(fileName);
}
@TestMetadata("AddImportsButNoConversion2.java")
public void testAddImportsButNoConversion2() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/copyPaste/conversion/AddImportsButNoConversion2.java");
doTest(fileName);
}
@TestMetadata("AddImportsClassInSamePackage.java")
public void testAddImportsClassInSamePackage() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/copyPaste/conversion/AddImportsClassInSamePackage.java");
doTest(fileName);
}
@TestMetadata("AddImportsDummyConflict.java")
public void testAddImportsDummyConflict() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/copyPaste/conversion/AddImportsDummyConflict.java");
doTest(fileName);
}
public void testAllFilesPresentInConversion() throws Exception { public void testAllFilesPresentInConversion() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/copyPaste/conversion"), Pattern.compile("^(.+)\\.java$"), true); JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/copyPaste/conversion"), Pattern.compile("^(.+)\\.java$"), true);
} }
+18 -7
View File
@@ -72,15 +72,26 @@ class Converter private(
private fun createDefaultCodeConverter() = CodeConverter(this, DefaultExpressionConverter(), DefaultStatementConverter(), null) private fun createDefaultCodeConverter() = CodeConverter(this, DefaultExpressionConverter(), DefaultStatementConverter(), null)
public fun convert(): ((Map<PsiElement, UsageProcessing>) -> String)? { public data class IntermediateResult(
val element = convertTopElement(elementToConvert) ?: return null val codeGenerator: (Map<PsiElement, UsageProcessing>) -> String,
return { usageProcessings -> val parseContext: ParseContext
unfoldDeferredElements(usageProcessings) )
val builder = CodeBuilder(elementToConvert) public fun convert(): IntermediateResult? {
builder.append(element) val element = convertTopElement(elementToConvert) ?: return null
builder.result val parseContext = when (elementToConvert) {
is PsiStatement, is PsiExpression -> ParseContext.CODE_BLOCK
else -> ParseContext.TOP_LEVEL
} }
return IntermediateResult(
{ usageProcessings ->
unfoldDeferredElements(usageProcessings)
val builder = CodeBuilder(elementToConvert)
builder.append(element)
builder.result
},
parseContext)
} }
private fun convertTopElement(element: PsiElement): Element? = when (element) { private fun convertTopElement(element: PsiElement): Element? = when (element) {
@@ -63,6 +63,11 @@ public trait PostProcessor {
public fun doAdditionalProcessing(file: JetFile, rangeMarker: RangeMarker?) public fun doAdditionalProcessing(file: JetFile, rangeMarker: RangeMarker?)
} }
public enum class ParseContext {
TOP_LEVEL
CODE_BLOCK
}
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,
@@ -75,13 +80,15 @@ public class JavaToKotlinConverter(private val project: Project,
val postProcessingContext: PsiElement? val postProcessingContext: PsiElement?
) )
public data class Result(val text: String, val parseContext: ParseContext)
public fun elementsToKotlin( public fun elementsToKotlin(
inputElements: List<InputElement>, inputElements: List<InputElement>,
progress: ProgressIndicator = EmptyProgressIndicator() progress: ProgressIndicator = EmptyProgressIndicator()
): List<String> { ): List<Result?> {
try { try {
val elementCount = inputElements.size() val elementCount = inputElements.size()
val intermediateResults = ArrayList<((Map<PsiElement, UsageProcessing>) -> String)?>(elementCount) val intermediateResults = ArrayList<(Converter.IntermediateResult)?>(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))
@@ -130,21 +137,27 @@ public class JavaToKotlinConverter(private val project: Project,
intermediateResults.add(result) intermediateResults.add(result)
} }
val results = ArrayList<String>(elementCount) val results = ArrayList<Result?>(elementCount)
processFilesWithProgress(0.25) { i -> processFilesWithProgress(0.25) { i ->
val result = intermediateResults[i] val result = intermediateResults[i]
results.add(if (result != null) result(usageProcessings) else "") results.add(if (result != null)
Result(result.codeGenerator(usageProcessings), result.parseContext)
else
null)
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>(elementCount) if (postProcessor == null) return results
val finalResults = ArrayList<Result?>(elementCount)
processFilesWithProgress(0.5) { i -> processFilesWithProgress(0.5) { i ->
val result = results[i] val result = results[i]
if (postProcessor != null) { if (result != null) {
try { try {
val kotlinFile = JetPsiFactory(project).createAnalyzableFile("dummy.kt", result, inputElements[i].postProcessingContext!!) //TODO: post processing does not work correctly for ParseContext different from TOP_LEVEL
val kotlinFile = JetPsiFactory(project).createAnalyzableFile("dummy.kt", result.text, inputElements[i].postProcessingContext!!)
AfterConversionPass(project, postProcessor).run(kotlinFile, null) AfterConversionPass(project, postProcessor).run(kotlinFile, null)
finalResults.add(kotlinFile.getText()) finalResults.add(Result(kotlinFile.getText(), result.parseContext))
} }
catch(e: ProcessCanceledException) { catch(e: ProcessCanceledException) {
throw e throw e
@@ -155,7 +168,7 @@ public class JavaToKotlinConverter(private val project: Project,
} }
} }
else { else {
finalResults.add(result) finalResults.add(null)
} }
} }
@@ -135,7 +135,7 @@ public object JavaToKotlinTranslator {
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, null) val converter = JavaToKotlinConverter(file.getProject(), ConverterSettings.defaultSettings, EmptyReferenceSearcher, EmptyResolverForConverter, null)
return prettify(converter.elementsToKotlin(listOf(JavaToKotlinConverter.InputElement(file, null))).single()) return prettify(converter.elementsToKotlin(listOf(JavaToKotlinConverter.InputElement(file, null))).single()!!.text)
} }
return "" return ""
} }
@@ -46,7 +46,7 @@ public abstract class AbstractJavaToKotlinConverterMultiFileTest : AbstractJavaT
val converter = JavaToKotlinConverter(project, ConverterSettings.defaultSettings, val converter = JavaToKotlinConverter(project, ConverterSettings.defaultSettings,
IdeaReferenceSearcher, IdeaResolverForConverter, J2kPostProcessor(formatCode = true)) IdeaReferenceSearcher, IdeaResolverForConverter, J2kPostProcessor(formatCode = true))
val inputElements = psiFiles.map { JavaToKotlinConverter.InputElement(it, it) } val inputElements = psiFiles.map { JavaToKotlinConverter.InputElement(it, it) }
val results: List<String> = converter.elementsToKotlin(inputElements) val results: List<String> = converter.elementsToKotlin(inputElements).map { it!!.text }
fun expectedFile(i: Int) = File(javaFiles[i].getPath().replace(".java", ".kt")) fun expectedFile(i: Int) = File(javaFiles[i].getPath().replace(".java", ".kt"))
@@ -109,7 +109,7 @@ public abstract class AbstractJavaToKotlinConverterSingleFileTest : AbstractJava
val converter = JavaToKotlinConverter(project, settings, val converter = JavaToKotlinConverter(project, settings,
IdeaReferenceSearcher, IdeaResolverForConverter, J2kPostProcessor(formatCode = false)) IdeaReferenceSearcher, IdeaResolverForConverter, J2kPostProcessor(formatCode = false))
val inputElements = listOf(JavaToKotlinConverter.InputElement(file, file)) val inputElements = listOf(JavaToKotlinConverter.InputElement(file, file))
return converter.elementsToKotlin(inputElements).single() return converter.elementsToKotlin(inputElements).single()!!.text
} }
private fun methodToKotlin(text: String, settings: ConverterSettings, project: Project): String { private fun methodToKotlin(text: String, settings: ConverterSettings, project: Project): String {