diff --git a/core/util.runtime/src/org/jetbrains/kotlin/utils/addToStdlib.kt b/core/util.runtime/src/org/jetbrains/kotlin/utils/addToStdlib.kt index 4cc9c439663..7e449aacdb4 100644 --- a/core/util.runtime/src/org/jetbrains/kotlin/utils/addToStdlib.kt +++ b/core/util.runtime/src/org/jetbrains/kotlin/utils/addToStdlib.kt @@ -121,3 +121,7 @@ inline fun Iterable.sumByLong(selector: (T) -> Long): Long { } return sum } + +inline fun , O> C.ifNotEmpty(body: C.() -> O?): O? = if (isNotEmpty()) this.body() else null + +inline fun Array.ifNotEmpty(body: Array.() -> O?): O? = if (isNotEmpty()) this.body() else null \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/conversion/copy/ConvertTextJavaCopyPasteProcessor.kt b/idea/src/org/jetbrains/kotlin/idea/conversion/copy/ConvertTextJavaCopyPasteProcessor.kt index 701f88e3773..b1bed56262e 100644 --- a/idea/src/org/jetbrains/kotlin/idea/conversion/copy/ConvertTextJavaCopyPasteProcessor.kt +++ b/idea/src/org/jetbrains/kotlin/idea/conversion/copy/ConvertTextJavaCopyPasteProcessor.kt @@ -27,24 +27,24 @@ import com.intellij.openapi.project.DumbService import com.intellij.openapi.project.Project import com.intellij.openapi.util.Ref import com.intellij.openapi.util.TextRange -import com.intellij.psi.PsiDocumentManager -import com.intellij.psi.PsiErrorElement -import com.intellij.psi.PsiFile -import com.intellij.psi.PsiFileFactory +import com.intellij.psi.* import com.intellij.util.LocalTimeCounter import org.jetbrains.annotations.TestOnly +import org.jetbrains.kotlin.asJava.toLightClass import org.jetbrains.kotlin.idea.KotlinFileType +import org.jetbrains.kotlin.idea.codeInsight.referenceExpression import org.jetbrains.kotlin.idea.editor.KotlinEditorOptions import org.jetbrains.kotlin.idea.j2k.J2kPostProcessor +import org.jetbrains.kotlin.idea.references.mainReference import org.jetbrains.kotlin.idea.util.application.runWriteAction import org.jetbrains.kotlin.j2k.AfterConversionPass import org.jetbrains.kotlin.lexer.KtTokens -import org.jetbrains.kotlin.psi.KtBlockExpression -import org.jetbrains.kotlin.psi.KtClassBody -import org.jetbrains.kotlin.psi.KtFile -import org.jetbrains.kotlin.psi.KtFunction +import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.anyDescendantOfType +import org.jetbrains.kotlin.psi.psiUtil.getParentOfType +import org.jetbrains.kotlin.resolve.ImportPath import org.jetbrains.kotlin.utils.addToStdlib.check +import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty import java.awt.datatransfer.DataFlavor import java.awt.datatransfer.Transferable @@ -96,13 +96,15 @@ class ConvertTextJavaCopyPasteProcessor : CopyPastePostProcessor KotlinContext.TOP_LEVEL + is KtClassBody -> KotlinContext.CLASS_BODY + is KtBlockExpression -> KotlinContext.IN_BLOCK + else -> KotlinContext.EXPRESSION + } + + private fun detectPasteTarget(file: KtFile, startOffset: Int, endOffset: Int): KtElement? { if (isNoConversionPosition(file, startOffset)) return null val fileText = file.text @@ -128,18 +144,13 @@ class ConvertTextJavaCopyPasteProcessor : CopyPastePostProcessor KotlinContext.TOP_LEVEL - is KtClassBody -> KotlinContext.CLASS_BODY - is KtBlockExpression -> KotlinContext.IN_BLOCK - else -> KotlinContext.EXPRESSION - } + return declaration.parent as? KtElement } private fun detectConversionContext(pasteContext: KotlinContext, text: String, project: Project): JavaContext? { @@ -200,26 +211,100 @@ class ConvertTextJavaCopyPasteProcessor : CopyPastePostProcessor createCopiedJavaCode("$", text) + private fun prepareCopiedJavaCodeByContext(text: String, context: JavaContext, target: KtElement): CopiedJavaCode { - JavaContext.CLASS_BODY -> createCopiedJavaCode("class Dummy {\n$\n}", text) + val targetFile = target.containingFile as KtFile - JavaContext.IN_BLOCK -> createCopiedJavaCode("class Dummy {\nvoid foo() {\n$\n}\n}", text) + val prefix = buildString { + targetFile.packageDirective?.let { + if (it.text.isNotEmpty()) { + append(it.text) + append(";\n") + } + } - JavaContext.EXPRESSION -> createCopiedJavaCode("class Dummy {\nObject field = $\n}", text) + fun appendImport(importPath: ImportPath) { + append("import ") + append(importPath) + append(";\n") + } + + fun appendStaticImport(importPath: ImportPath) { + append("import static ") + append(importPath) + append(";\n") + } + + targetFile.importDirectives.forEach { + val importPath = it.importPath + val importedReference = it.importedReference + if (importPath != null && !importPath.hasAlias() && importedReference is KtDotQualifiedExpression) { + + val receiver = importedReference + .receiverExpression + .referenceExpression() + ?.mainReference + ?.resolve() + val selector = importedReference + .selectorExpression + ?.referenceExpression() + ?.mainReference + ?.resolve() + + val isPackageReceiver = receiver is PsiPackage + val isClassReceiver = receiver is PsiClass + val isClassSelector = selector is PsiClass + + if (importPath.isAllUnder) { + if (isClassReceiver) + appendStaticImport(importPath) + else if (isPackageReceiver) + appendImport(importPath) + } + else { + if (isPackageReceiver && isClassSelector) + appendImport(importPath) + else if (isClassReceiver) + appendStaticImport(importPath) + } + } + } } - val dataForConversion = DataForConversion.prepare(copiedJavaCode, project) - val conversionResult = convertCopiedCodeToKotlin(dataForConversion.elementsAndTexts, project) - return conversionResult.text + val classDef = when (context) { + JavaContext.TOP_LEVEL -> "" + + JavaContext.CLASS_BODY, + JavaContext.IN_BLOCK, + JavaContext.EXPRESSION -> { + val lightClass = target.getParentOfType(false)?.toLightClass() + + buildString { + append("class ") + append(lightClass?.name ?: "Dummy") + lightClass?.extendsListTypes?.ifNotEmpty { joinTo(this@buildString, prefix = " extends ") { it.getCanonicalText(true) } } + lightClass?.implementsListTypes?.ifNotEmpty { joinTo(this@buildString, prefix = " implements ") { it.getCanonicalText(true) } } + } + } + } + + val copiedJavaCode = when (context) { + JavaContext.TOP_LEVEL -> createCopiedJavaCode(prefix, "$", text) + + JavaContext.CLASS_BODY -> createCopiedJavaCode(prefix, "$classDef {\n$\n}", text) + + JavaContext.IN_BLOCK -> createCopiedJavaCode(prefix, "$classDef {\nvoid foo() {\n$\n}\n}", text) + + JavaContext.EXPRESSION -> createCopiedJavaCode(prefix, "$classDef {\nObject field = $\n}", text) + } + return copiedJavaCode } - private fun createCopiedJavaCode(template: String, text: String): CopiedJavaCode { + private fun createCopiedJavaCode(prefix: String, templateWithoutPrefix: String, text: String): CopiedJavaCode { + val template = "$prefix\n$templateWithoutPrefix" val index = template.indexOf("$") assert(index >= 0) val fileText = template.substring(0, index) + text + template.substring(index + 1) diff --git a/idea/testData/copyPaste/plainTextConversion/Override.dependency.java b/idea/testData/copyPaste/plainTextConversion/Override.dependency.java new file mode 100644 index 00000000000..7527edd8a75 --- /dev/null +++ b/idea/testData/copyPaste/plainTextConversion/Override.dependency.java @@ -0,0 +1,5 @@ +package test; + +public class JavaParent { + public void subject() {} +} diff --git a/idea/testData/copyPaste/plainTextConversion/Override.expected.kt b/idea/testData/copyPaste/plainTextConversion/Override.expected.kt new file mode 100644 index 00000000000..05818aefdcc --- /dev/null +++ b/idea/testData/copyPaste/plainTextConversion/Override.expected.kt @@ -0,0 +1,6 @@ +import test.JavaParent + +class KotlinChild : JavaParent() { + override fun subject() { + } +} \ No newline at end of file diff --git a/idea/testData/copyPaste/plainTextConversion/Override.to.kt b/idea/testData/copyPaste/plainTextConversion/Override.to.kt new file mode 100644 index 00000000000..6ea3d0de688 --- /dev/null +++ b/idea/testData/copyPaste/plainTextConversion/Override.to.kt @@ -0,0 +1,5 @@ +import test.JavaParent + +class KotlinChild : JavaParent() { + +} \ No newline at end of file diff --git a/idea/testData/copyPaste/plainTextConversion/Override.txt b/idea/testData/copyPaste/plainTextConversion/Override.txt new file mode 100644 index 00000000000..018332ad677 --- /dev/null +++ b/idea/testData/copyPaste/plainTextConversion/Override.txt @@ -0,0 +1 @@ +@Override public void subject() {} \ No newline at end of file diff --git a/idea/testData/copyPaste/plainTextConversion/OverrideInterface.dependency.java b/idea/testData/copyPaste/plainTextConversion/OverrideInterface.dependency.java new file mode 100644 index 00000000000..cf6159d5eab --- /dev/null +++ b/idea/testData/copyPaste/plainTextConversion/OverrideInterface.dependency.java @@ -0,0 +1,7 @@ +package test; + +import org.jetbrains.annotations.Nullable; + +public interface JavaInterface { + void subject(@Nullable String s) +} diff --git a/idea/testData/copyPaste/plainTextConversion/OverrideInterface.expected.kt b/idea/testData/copyPaste/plainTextConversion/OverrideInterface.expected.kt new file mode 100644 index 00000000000..b5f124b8376 --- /dev/null +++ b/idea/testData/copyPaste/plainTextConversion/OverrideInterface.expected.kt @@ -0,0 +1,6 @@ +import test.JavaInterface + +class KotlinChild : JavaInterface() { + override fun subject(s: String?) { + } +} \ No newline at end of file diff --git a/idea/testData/copyPaste/plainTextConversion/OverrideInterface.to.kt b/idea/testData/copyPaste/plainTextConversion/OverrideInterface.to.kt new file mode 100644 index 00000000000..8dbc5b3f3b6 --- /dev/null +++ b/idea/testData/copyPaste/plainTextConversion/OverrideInterface.to.kt @@ -0,0 +1,5 @@ +import test.JavaInterface + +class KotlinChild : JavaInterface() { + +} \ No newline at end of file diff --git a/idea/testData/copyPaste/plainTextConversion/OverrideInterface.txt b/idea/testData/copyPaste/plainTextConversion/OverrideInterface.txt new file mode 100644 index 00000000000..d8a6cbf58b8 --- /dev/null +++ b/idea/testData/copyPaste/plainTextConversion/OverrideInterface.txt @@ -0,0 +1 @@ +@Override public void subject(String s) {} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/conversion/copy/AbstractTextJavaToKotlinCopyPasteConversionTest.kt b/idea/tests/org/jetbrains/kotlin/idea/conversion/copy/AbstractTextJavaToKotlinCopyPasteConversionTest.kt index 7a925c71c03..b8458947c4f 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/conversion/copy/AbstractTextJavaToKotlinCopyPasteConversionTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/conversion/copy/AbstractTextJavaToKotlinCopyPasteConversionTest.kt @@ -57,6 +57,9 @@ abstract class AbstractTextJavaToKotlinCopyPasteConversionTest : AbstractCopyPas myFixture.editor.selectionModel.setSelection(0, fileText.length) myFixture.performEditorAction(IdeActions.ACTION_COPY) + configureByDependencyIfExists(testName + ".dependency.kt") + configureByDependencyIfExists(testName + ".dependency.java") + configureTargetFile(testName + ".to.kt") ConvertTextJavaCopyPasteProcessor.conversionPerformed = false diff --git a/idea/tests/org/jetbrains/kotlin/idea/conversion/copy/TextJavaToKotlinCopyPasteConversionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/conversion/copy/TextJavaToKotlinCopyPasteConversionTestGenerated.java index 0806c5e32f1..35674022806 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/conversion/copy/TextJavaToKotlinCopyPasteConversionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/conversion/copy/TextJavaToKotlinCopyPasteConversionTestGenerated.java @@ -95,6 +95,18 @@ public class TextJavaToKotlinCopyPasteConversionTestGenerated extends AbstractTe doTest(fileName); } + @TestMetadata("Override.txt") + public void testOverride() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/copyPaste/plainTextConversion/Override.txt"); + doTest(fileName); + } + + @TestMetadata("OverrideInterface.txt") + public void testOverrideInterface() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/copyPaste/plainTextConversion/OverrideInterface.txt"); + doTest(fileName); + } + @TestMetadata("PostProcessing.txt") public void testPostProcessing() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/copyPaste/plainTextConversion/PostProcessing.txt");