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))
val inputElements = javaFiles.map { JavaToKotlinConverter.InputElement(it, it) }
convertedTexts = converter.elementsToKotlin(inputElements, ProgressManager.getInstance().getProgressIndicator())
.map { it!!.text /*conversion of a file always succeeds*/ }
}
if (!ProgressManager.getInstance().runProcessWithProgressSynchronously(
@@ -85,11 +85,7 @@ public class KotlinCopyPasteReferenceProcessor() : CopyPastePostProcessor<Kotlin
if (file !is JetFile || DumbService.getInstance(file.getProject()).isDumb()) return listOf()
val collectedData = try {
toTextRanges(startOffsets, endOffsets).flatMap {
CollectHighlightsUtil.getElementsInRange(file, it.start, it.end).flatMap { element ->
collectReferenceDataFromElement(element, file, it.start, startOffsets, endOffsets)
}
}
collectReferenceData(file, startOffsets, endOffsets)
}
catch (e: ProcessCanceledException) {
// supposedly analysis can only be canceled from another thread
@@ -107,23 +103,36 @@ public class KotlinCopyPasteReferenceProcessor() : CopyPastePostProcessor<Kotlin
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,
file: JetFile,
startOffset: Int,
startOffsets: 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
//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) {
val declarations = DescriptorToSourceUtilsIde.getAllDeclarations(file.getProject(), descriptor)
val declaration = declarations.singleOrNull()
@@ -139,9 +148,8 @@ public class KotlinCopyPasteReferenceProcessor() : CopyPastePostProcessor<Kotlin
if (!descriptor.canBeReferencedViaImport()) 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? {
@@ -179,12 +187,16 @@ public class KotlinCopyPasteReferenceProcessor() : CopyPastePostProcessor<Kotlin
val file = PsiDocumentManager.getInstance(project).getPsiFile(document)
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()
assert(values.size() == 1)
val referenceData = values.single().data
val referencesPossibleToRestore = findReferencesToRestore(file, bounds, referenceData)
val referencesPossibleToRestore = findReferencesToRestore(file, boundStart, referenceData)
val selectedReferencesToRestore = showRestoreReferencesDialog(project, referencesPossibleToRestore)
if (selectedReferencesToRestore.isEmpty()) return
@@ -192,14 +204,13 @@ public class KotlinCopyPasteReferenceProcessor() : CopyPastePostProcessor<Kotlin
ApplicationManager.getApplication()!!.runWriteAction(Runnable {
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()
return referenceData.map {
val referenceElement = findReference(it, file, bounds)
val referenceElement = findReference(it, file, boundStart)
if (referenceElement != null)
createReferenceToRestoreData(referenceElement, it)
else
@@ -207,9 +218,9 @@ public class KotlinCopyPasteReferenceProcessor() : CopyPastePostProcessor<Kotlin
}.filterNotNull()
}
private fun findReference(data: KotlinReferenceData, file: JetFile, bounds: RangeMarker): JetElement? {
val startOffset = data.startOffset + bounds.getStartOffset()
val endOffset = data.endOffset + bounds.getStartOffset()
private fun findReference(data: KotlinReferenceData, file: JetFile, boundStart: Int): JetElement? {
val startOffset = data.startOffset + boundStart
val endOffset = data.endOffset + boundStart
val element = file.findElementAt(startOffset)
val desiredRange = TextRange(startOffset, endOffset)
var expression = element
@@ -28,17 +28,18 @@ import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Ref
import com.intellij.openapi.util.TextRange
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.j2k.IdeaResolverForConverter
import org.jetbrains.kotlin.idea.j2k.J2kPostProcessor
import org.jetbrains.kotlin.j2k.AfterConversionPass
import org.jetbrains.kotlin.j2k.ConverterSettings
import org.jetbrains.kotlin.j2k.IdeaReferenceSearcher
import org.jetbrains.kotlin.j2k.JavaToKotlinConverter
import org.jetbrains.kotlin.j2k.*
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.FqNameUnsafe
import org.jetbrains.kotlin.platform.JavaToKotlinClassMap
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.parents
import java.awt.datatransfer.Transferable
import java.util.ArrayList
@@ -69,43 +70,80 @@ public class ConvertJavaCopyPastePostProcessor : CopyPastePostProcessor<TextBloc
val data = values.single()
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).
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] }
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 needConvert = jetEditorOptions.isEnableJavaToKotlinConversion() && (jetEditorOptions.isDonTShowConversionDialog() || okFromDialog(project))
if (needConvert) {
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 {
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)
var newBounds = insertImports(TextRange(startOffset, endOffsetAfterCopy), referenceData)
PsiDocumentManager.getInstance(project).commitAllDocuments()
AfterConversionPass(project, J2kPostProcessor(formatCode = true)).run(targetFile, TextRange(startOffset, endOffsetAfterCopy))
AfterConversionPass(project, J2kPostProcessor(formatCode = true)).run(targetFile, newBounds)
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")
val sourceFileText = code.fileText
@@ -114,8 +152,6 @@ public class ConvertJavaCopyPastePostProcessor : CopyPastePostProcessor<TextBloc
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(
sourceFile.getProject(),
ConverterSettings.defaultSettings,
@@ -130,12 +166,16 @@ public class ConvertJavaCopyPastePostProcessor : CopyPastePostProcessor<TextBloc
var resultIndex = 0
val convertedCodeBuilder = StringBuilder()
val originalCodeBuilder = StringBuilder()
var parseContext: ParseContext? = null
for (o in list) {
if (o is PsiElement) {
val originalText = o.getText()
val result = results[resultIndex++]
if (!result.isEmpty()) {
convertedCodeBuilder.append(result)
if (result != null) {
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"
convertedCodeBuilder.append(originalText)
@@ -150,9 +190,7 @@ public class ConvertJavaCopyPastePostProcessor : CopyPastePostProcessor<TextBloc
val convertedCode = convertedCodeBuilder.toString()
val originalCode = originalCodeBuilder.toString()
if (convertedCode == originalCode) return null
return convertedCode
return ConversionResult(convertedCode, parseContext ?: ParseContext.TOP_LEVEL, convertedCode != originalCode)
}
// 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 {
val dialog = KotlinPasteFromJavaDialog(project)
dialog.show()
@@ -16,6 +16,7 @@
package org.jetbrains.kotlin.idea.conversion.copy
import com.intellij.openapi.editor.RangeMarker
import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiElement
@@ -29,3 +30,6 @@ public val TextRange.end: Int
public val PsiElement.range: TextRange
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){
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 {
public static void main(String[] args) {
qualifier.<selection>reference(arg)</selection>;
@@ -1 +1 @@
No conversion performed
}
@@ -1,3 +1,4 @@
// NO_CONVERSION_EXPECTED
class A {
public static void main(String[] args) {
//sdasd
@@ -1 +1,4 @@
No conversion performed
fun main(args: Array<String>) {
qualifier
somethingElse()
}
@@ -1,3 +1,4 @@
// NO_CONVERSION_EXPECTED
class A {
public static void main(String[] args) {
<selection>qualifier</selection>.reference(arg);
@@ -1 +1 @@
No conversion performed
class Some
@@ -1,3 +1,4 @@
// NO_CONVERSION_EXPECTED
<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;
import com.intellij.codeInsight.CodeInsightSettings;
import com.intellij.openapi.actionSystem.IdeActions;
import com.intellij.openapi.util.TextRange;
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.Errors;
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.caches.resolve.ResolvePackage;
import org.jetbrains.kotlin.psi.JetFile;
@@ -38,26 +37,11 @@ import org.jetbrains.kotlin.test.InTextDirectivesUtils;
import java.io.File;
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 DEFAULT_TO_FILE_TEXT = "package to\n\n<caret>";
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 {
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")
@RunWith(JUnit3RunnerWithInners.class)
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 {
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)
public fun convert(): ((Map<PsiElement, UsageProcessing>) -> String)? {
val element = convertTopElement(elementToConvert) ?: return null
return { usageProcessings ->
unfoldDeferredElements(usageProcessings)
public data class IntermediateResult(
val codeGenerator: (Map<PsiElement, UsageProcessing>) -> String,
val parseContext: ParseContext
)
val builder = CodeBuilder(elementToConvert)
builder.append(element)
builder.result
public fun convert(): IntermediateResult? {
val element = convertTopElement(elementToConvert) ?: return null
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) {
@@ -63,6 +63,11 @@ public trait PostProcessor {
public fun doAdditionalProcessing(file: JetFile, rangeMarker: RangeMarker?)
}
public enum class ParseContext {
TOP_LEVEL
CODE_BLOCK
}
public class JavaToKotlinConverter(private val project: Project,
private val settings: ConverterSettings,
private val referenceSearcher: ReferenceSearcher,
@@ -75,13 +80,15 @@ public class JavaToKotlinConverter(private val project: Project,
val postProcessingContext: PsiElement?
)
public data class Result(val text: String, val parseContext: ParseContext)
public fun elementsToKotlin(
inputElements: List<InputElement>,
progress: ProgressIndicator = EmptyProgressIndicator()
): List<String> {
): List<Result?> {
try {
val elementCount = inputElements.size()
val intermediateResults = ArrayList<((Map<PsiElement, UsageProcessing>) -> String)?>(elementCount)
val intermediateResults = ArrayList<(Converter.IntermediateResult)?>(elementCount)
val usageProcessings = HashMap<PsiElement, UsageProcessing>()
val usageProcessingCollector: (UsageProcessing) -> Unit = { usageProcessing ->
assert(!usageProcessings.containsKey(usageProcessing.targetElement))
@@ -130,21 +137,27 @@ public class JavaToKotlinConverter(private val project: Project,
intermediateResults.add(result)
}
val results = ArrayList<String>(elementCount)
val results = ArrayList<Result?>(elementCount)
processFilesWithProgress(0.25) { 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
}
val finalResults = ArrayList<String>(elementCount)
if (postProcessor == null) return results
val finalResults = ArrayList<Result?>(elementCount)
processFilesWithProgress(0.5) { i ->
val result = results[i]
if (postProcessor != null) {
if (result != null) {
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)
finalResults.add(kotlinFile.getText())
finalResults.add(Result(kotlinFile.getText(), result.parseContext))
}
catch(e: ProcessCanceledException) {
throw e
@@ -155,7 +168,7 @@ public class JavaToKotlinConverter(private val project: Project,
}
}
else {
finalResults.add(result)
finalResults.add(null)
}
}
@@ -135,7 +135,7 @@ public object JavaToKotlinTranslator {
val file = createFile(javaCode)
if (file is PsiJavaFile) {
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 ""
}
@@ -46,7 +46,7 @@ public abstract class AbstractJavaToKotlinConverterMultiFileTest : AbstractJavaT
val converter = JavaToKotlinConverter(project, ConverterSettings.defaultSettings,
IdeaReferenceSearcher, IdeaResolverForConverter, J2kPostProcessor(formatCode = true))
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"))
@@ -109,7 +109,7 @@ public abstract class AbstractJavaToKotlinConverterSingleFileTest : AbstractJava
val converter = JavaToKotlinConverter(project, settings,
IdeaReferenceSearcher, IdeaResolverForConverter, J2kPostProcessor(formatCode = false))
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 {