diff --git a/idea/src/org/jetbrains/kotlin/idea/actions/JavaToKotlinAction.kt b/idea/src/org/jetbrains/kotlin/idea/actions/JavaToKotlinAction.kt index a1185eb4746..fd217cfc90d 100644 --- a/idea/src/org/jetbrains/kotlin/idea/actions/JavaToKotlinAction.kt +++ b/idea/src/org/jetbrains/kotlin/idea/actions/JavaToKotlinAction.kt @@ -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( diff --git a/idea/src/org/jetbrains/kotlin/idea/codeInsight/KotlinCopyPasteReferenceProcessor.kt b/idea/src/org/jetbrains/kotlin/idea/codeInsight/KotlinCopyPasteReferenceProcessor.kt index 2af80f9db83..ac0da71dc17 100644 --- a/idea/src/org/jetbrains/kotlin/idea/codeInsight/KotlinCopyPasteReferenceProcessor.kt +++ b/idea/src/org/jetbrains/kotlin/idea/codeInsight/KotlinCopyPasteReferenceProcessor.kt @@ -85,11 +85,7 @@ public class KotlinCopyPasteReferenceProcessor() : CopyPastePostProcessor - 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 { + val result = ArrayList() + 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.addReferenceDataFromElement( element: PsiElement, file: JetFile, startOffset: Int, startOffsets: IntArray, endOffsets: IntArray - ): Collection { + ) { - 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() for (descriptor in descriptors) { val declarations = DescriptorToSourceUtilsIde.getAllDeclarations(file.getProject(), descriptor) val declaration = declarations.singleOrNull() @@ -139,9 +148,8 @@ public class KotlinCopyPasteReferenceProcessor() : CopyPastePostProcessor) { 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): List { + private fun findReferencesToRestore(file: PsiFile, boundStart: Int, referenceData: Array): List { 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> { + //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): 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>? = 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 { + 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() diff --git a/idea/src/org/jetbrains/kotlin/idea/conversion/copy/RangeUtils.kt b/idea/src/org/jetbrains/kotlin/idea/conversion/copy/RangeUtils.kt index 05ef97d8a4e..a8a54f6dda8 100644 --- a/idea/src/org/jetbrains/kotlin/idea/conversion/copy/RangeUtils.kt +++ b/idea/src/org/jetbrains/kotlin/idea/conversion/copy/RangeUtils.kt @@ -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 diff --git a/idea/testData/copyPaste/conversion/AddImports.expected.kt b/idea/testData/copyPaste/conversion/AddImports.expected.kt new file mode 100644 index 00000000000..216bd16c88e --- /dev/null +++ b/idea/testData/copyPaste/conversion/AddImports.expected.kt @@ -0,0 +1,5 @@ +import java.io.File + +fun foo(file: File): List { + return emptyList() +} diff --git a/idea/testData/copyPaste/conversion/AddImports.java b/idea/testData/copyPaste/conversion/AddImports.java new file mode 100644 index 00000000000..1f9d9b61bd3 --- /dev/null +++ b/idea/testData/copyPaste/conversion/AddImports.java @@ -0,0 +1,9 @@ +import java.io.File; +import java.util.Collections; +import java.util.List; + +class C { + List foo(File file) { + return Collections.emptyList(); + } +} \ No newline at end of file diff --git a/idea/testData/copyPaste/conversion/AddImports.to.kt b/idea/testData/copyPaste/conversion/AddImports.to.kt new file mode 100644 index 00000000000..81389d50841 --- /dev/null +++ b/idea/testData/copyPaste/conversion/AddImports.to.kt @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/idea/testData/copyPaste/conversion/AddImportsButNoConversion.expected.kt b/idea/testData/copyPaste/conversion/AddImportsButNoConversion.expected.kt new file mode 100644 index 00000000000..8b3c431f711 --- /dev/null +++ b/idea/testData/copyPaste/conversion/AddImportsButNoConversion.expected.kt @@ -0,0 +1,3 @@ +import java.io.File + +fun foo(): List \ No newline at end of file diff --git a/idea/testData/copyPaste/conversion/AddImportsButNoConversion.java b/idea/testData/copyPaste/conversion/AddImportsButNoConversion.java new file mode 100644 index 00000000000..4bb93dbb9d2 --- /dev/null +++ b/idea/testData/copyPaste/conversion/AddImportsButNoConversion.java @@ -0,0 +1,10 @@ +// NO_CONVERSION_EXPECTED +import java.io.*; +import java.util.Collections; +import java.util.List; + +class C { + List foo() { + return Collections.emptyList(); + } +} \ No newline at end of file diff --git a/idea/testData/copyPaste/conversion/AddImportsButNoConversion.to.kt b/idea/testData/copyPaste/conversion/AddImportsButNoConversion.to.kt new file mode 100644 index 00000000000..9ef6a3c24c5 --- /dev/null +++ b/idea/testData/copyPaste/conversion/AddImportsButNoConversion.to.kt @@ -0,0 +1 @@ +fun foo(): \ No newline at end of file diff --git a/idea/testData/copyPaste/conversion/AddImportsButNoConversion2.expected.kt b/idea/testData/copyPaste/conversion/AddImportsButNoConversion2.expected.kt new file mode 100644 index 00000000000..c7320826a4b --- /dev/null +++ b/idea/testData/copyPaste/conversion/AddImportsButNoConversion2.expected.kt @@ -0,0 +1,5 @@ +import java.io.File + +fun foo() { + list.add(File.separatorChar) +} \ No newline at end of file diff --git a/idea/testData/copyPaste/conversion/AddImportsButNoConversion2.java b/idea/testData/copyPaste/conversion/AddImportsButNoConversion2.java new file mode 100644 index 00000000000..8b59449479b --- /dev/null +++ b/idea/testData/copyPaste/conversion/AddImportsButNoConversion2.java @@ -0,0 +1,9 @@ +// NO_CONVERSION_EXPECTED +import java.io.*; +import java.util.*; + +class C { + void foo(List list) { + list.add(File.separatorChar) + } +} \ No newline at end of file diff --git a/idea/testData/copyPaste/conversion/AddImportsButNoConversion2.to.kt b/idea/testData/copyPaste/conversion/AddImportsButNoConversion2.to.kt new file mode 100644 index 00000000000..94c413776f2 --- /dev/null +++ b/idea/testData/copyPaste/conversion/AddImportsButNoConversion2.to.kt @@ -0,0 +1,3 @@ +fun foo() { + +} \ No newline at end of file diff --git a/idea/testData/copyPaste/conversion/AddImportsClassInSamePackage.dependency.kt b/idea/testData/copyPaste/conversion/AddImportsClassInSamePackage.dependency.kt new file mode 100644 index 00000000000..2e610815a3a --- /dev/null +++ b/idea/testData/copyPaste/conversion/AddImportsClassInSamePackage.dependency.kt @@ -0,0 +1,8 @@ +package ppp + +import kotlin.platform.platformStatic + +public object Dependency { + [platformStatic] + public val FIELD: Int = 1 +} \ No newline at end of file diff --git a/idea/testData/copyPaste/conversion/AddImportsClassInSamePackage.expected.kt b/idea/testData/copyPaste/conversion/AddImportsClassInSamePackage.expected.kt new file mode 100644 index 00000000000..1aff96c92d6 --- /dev/null +++ b/idea/testData/copyPaste/conversion/AddImportsClassInSamePackage.expected.kt @@ -0,0 +1,5 @@ +import ppp.Dependency + +fun f() { + return Dependency.FIELD +} \ No newline at end of file diff --git a/idea/testData/copyPaste/conversion/AddImportsClassInSamePackage.java b/idea/testData/copyPaste/conversion/AddImportsClassInSamePackage.java new file mode 100644 index 00000000000..33c097e84b5 --- /dev/null +++ b/idea/testData/copyPaste/conversion/AddImportsClassInSamePackage.java @@ -0,0 +1,7 @@ +package ppp + +class C { + char foo() { + return Dependency.FIELD; + } +} \ No newline at end of file diff --git a/idea/testData/copyPaste/conversion/AddImportsClassInSamePackage.to.kt b/idea/testData/copyPaste/conversion/AddImportsClassInSamePackage.to.kt new file mode 100644 index 00000000000..b3a18cebd0c --- /dev/null +++ b/idea/testData/copyPaste/conversion/AddImportsClassInSamePackage.to.kt @@ -0,0 +1,3 @@ +fun f() { + +} \ No newline at end of file diff --git a/idea/testData/copyPaste/conversion/AddImportsDummyConflict.dependency.kt b/idea/testData/copyPaste/conversion/AddImportsDummyConflict.dependency.kt new file mode 100644 index 00000000000..7f4d9475f93 --- /dev/null +++ b/idea/testData/copyPaste/conversion/AddImportsDummyConflict.dependency.kt @@ -0,0 +1,3 @@ +package ppp + +class dummy0 \ No newline at end of file diff --git a/idea/testData/copyPaste/conversion/AddImportsDummyConflict.expected.kt b/idea/testData/copyPaste/conversion/AddImportsDummyConflict.expected.kt new file mode 100644 index 00000000000..6af6b20a805 --- /dev/null +++ b/idea/testData/copyPaste/conversion/AddImportsDummyConflict.expected.kt @@ -0,0 +1,5 @@ +import ppp.dummy0 + +fun f() { + return dummy0() +} \ No newline at end of file diff --git a/idea/testData/copyPaste/conversion/AddImportsDummyConflict.java b/idea/testData/copyPaste/conversion/AddImportsDummyConflict.java new file mode 100644 index 00000000000..ca4865d9d7a --- /dev/null +++ b/idea/testData/copyPaste/conversion/AddImportsDummyConflict.java @@ -0,0 +1,7 @@ +import ppp.* + +class C { + dummy0 foo() { + return dummy0(); + } +} \ No newline at end of file diff --git a/idea/testData/copyPaste/conversion/AddImportsDummyConflict.to.kt b/idea/testData/copyPaste/conversion/AddImportsDummyConflict.to.kt new file mode 100644 index 00000000000..b3a18cebd0c --- /dev/null +++ b/idea/testData/copyPaste/conversion/AddImportsDummyConflict.to.kt @@ -0,0 +1,3 @@ +fun f() { + +} \ No newline at end of file diff --git a/idea/testData/copyPaste/conversion/Arithmetic.expected.kt b/idea/testData/copyPaste/conversion/Arithmetic.expected.kt index 00397435237..02bf611c74f 100644 --- a/idea/testData/copyPaste/conversion/Arithmetic.expected.kt +++ b/idea/testData/copyPaste/conversion/Arithmetic.expected.kt @@ -1 +1 @@ -No conversion performed \ No newline at end of file +val v = b - c + d \ No newline at end of file diff --git a/idea/testData/copyPaste/conversion/Arithmetic.java b/idea/testData/copyPaste/conversion/Arithmetic.java index eec7aac9280..f82b8852710 100644 --- a/idea/testData/copyPaste/conversion/Arithmetic.java +++ b/idea/testData/copyPaste/conversion/Arithmetic.java @@ -1,3 +1,4 @@ +// NO_CONVERSION_EXPECTED public static void main(String[]args){ a + b - c + d - e } \ No newline at end of file diff --git a/idea/testData/copyPaste/conversion/MethodReferenceWithoutQualifier.expected.kt b/idea/testData/copyPaste/conversion/MethodReferenceWithoutQualifier.expected.kt index 00397435237..636a2ba7a5d 100644 --- a/idea/testData/copyPaste/conversion/MethodReferenceWithoutQualifier.expected.kt +++ b/idea/testData/copyPaste/conversion/MethodReferenceWithoutQualifier.expected.kt @@ -1 +1,4 @@ -No conversion performed \ No newline at end of file +fun main(args: Array) { + reference(arg) + somethingElse() +} \ No newline at end of file diff --git a/idea/testData/copyPaste/conversion/MethodReferenceWithoutQualifier.java b/idea/testData/copyPaste/conversion/MethodReferenceWithoutQualifier.java index 09ed4a6d46d..8bc2bfb8b06 100644 --- a/idea/testData/copyPaste/conversion/MethodReferenceWithoutQualifier.java +++ b/idea/testData/copyPaste/conversion/MethodReferenceWithoutQualifier.java @@ -1,3 +1,4 @@ +// NO_CONVERSION_EXPECTED class A { public static void main(String[] args) { qualifier.reference(arg); diff --git a/idea/testData/copyPaste/conversion/OnlyClosingBrace.expected.kt b/idea/testData/copyPaste/conversion/OnlyClosingBrace.expected.kt index 00397435237..ff30235f076 100644 --- a/idea/testData/copyPaste/conversion/OnlyClosingBrace.expected.kt +++ b/idea/testData/copyPaste/conversion/OnlyClosingBrace.expected.kt @@ -1 +1 @@ -No conversion performed \ No newline at end of file +} \ No newline at end of file diff --git a/idea/testData/copyPaste/conversion/OnlyClosingBrace.java b/idea/testData/copyPaste/conversion/OnlyClosingBrace.java index 8feec292e18..f2c1a88707c 100644 --- a/idea/testData/copyPaste/conversion/OnlyClosingBrace.java +++ b/idea/testData/copyPaste/conversion/OnlyClosingBrace.java @@ -1,3 +1,4 @@ +// NO_CONVERSION_EXPECTED class A { public static void main(String[] args) { //sdasd diff --git a/idea/testData/copyPaste/conversion/OnlyQualifier.expected.kt b/idea/testData/copyPaste/conversion/OnlyQualifier.expected.kt index 00397435237..0c1766e04c4 100644 --- a/idea/testData/copyPaste/conversion/OnlyQualifier.expected.kt +++ b/idea/testData/copyPaste/conversion/OnlyQualifier.expected.kt @@ -1 +1,4 @@ -No conversion performed \ No newline at end of file +fun main(args: Array) { + qualifier + somethingElse() +} \ No newline at end of file diff --git a/idea/testData/copyPaste/conversion/OnlyQualifier.java b/idea/testData/copyPaste/conversion/OnlyQualifier.java index 300ee6e06f2..b7482511db4 100644 --- a/idea/testData/copyPaste/conversion/OnlyQualifier.java +++ b/idea/testData/copyPaste/conversion/OnlyQualifier.java @@ -1,3 +1,4 @@ +// NO_CONVERSION_EXPECTED class A { public static void main(String[] args) { qualifier.reference(arg); diff --git a/idea/testData/copyPaste/conversion/SingleWordFromIdentifier.expected.kt b/idea/testData/copyPaste/conversion/SingleWordFromIdentifier.expected.kt index 00397435237..0bfdbcff9f7 100644 --- a/idea/testData/copyPaste/conversion/SingleWordFromIdentifier.expected.kt +++ b/idea/testData/copyPaste/conversion/SingleWordFromIdentifier.expected.kt @@ -1 +1 @@ -No conversion performed \ No newline at end of file +class Some \ No newline at end of file diff --git a/idea/testData/copyPaste/conversion/SingleWordFromIdentifier.java b/idea/testData/copyPaste/conversion/SingleWordFromIdentifier.java index 8ccb50accb5..3dfd2cb4b1d 100644 --- a/idea/testData/copyPaste/conversion/SingleWordFromIdentifier.java +++ b/idea/testData/copyPaste/conversion/SingleWordFromIdentifier.java @@ -1,3 +1,4 @@ +// NO_CONVERSION_EXPECTED class SomeInterestingClass { } \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/AbstractCopyPasteTest.kt b/idea/tests/org/jetbrains/kotlin/idea/AbstractCopyPasteTest.kt new file mode 100644 index 00000000000..ece8aafb960 --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/AbstractCopyPasteTest.kt @@ -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() + } +} + diff --git a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/AbstractInsertImportOnPasteTest.java b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/AbstractInsertImportOnPasteTest.java index 2a4f2c63ccc..1429acd8bea 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/AbstractInsertImportOnPasteTest.java +++ b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/AbstractInsertImportOnPasteTest.java @@ -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"; 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); } diff --git a/idea/tests/org/jetbrains/kotlin/idea/conversion/copy/AbstractJavaToKotlinCopyPasteConversionTest.java b/idea/tests/org/jetbrains/kotlin/idea/conversion/copy/AbstractJavaToKotlinCopyPasteConversionTest.java deleted file mode 100644 index ba5cf5036dc..00000000000 --- a/idea/tests/org/jetbrains/kotlin/idea/conversion/copy/AbstractJavaToKotlinCopyPasteConversionTest.java +++ /dev/null @@ -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"); - } -} diff --git a/idea/tests/org/jetbrains/kotlin/idea/conversion/copy/AbstractJavaToKotlinCopyPasteConversionTest.kt b/idea/tests/org/jetbrains/kotlin/idea/conversion/copy/AbstractJavaToKotlinCopyPasteConversionTest.kt new file mode 100644 index 00000000000..0edf8f0b060 --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/conversion/copy/AbstractJavaToKotlinCopyPasteConversionTest.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") + } +} diff --git a/idea/tests/org/jetbrains/kotlin/idea/conversion/copy/JavaToKotlinCopyPasteConversionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/conversion/copy/JavaToKotlinCopyPasteConversionTestGenerated.java index ae940b15cbc..e091b476373 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/conversion/copy/JavaToKotlinCopyPasteConversionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/conversion/copy/JavaToKotlinCopyPasteConversionTestGenerated.java @@ -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); } diff --git a/j2k/src/org/jetbrains/kotlin/j2k/Converter.kt b/j2k/src/org/jetbrains/kotlin/j2k/Converter.kt index a3ac304764e..3a6183f5060 100644 --- a/j2k/src/org/jetbrains/kotlin/j2k/Converter.kt +++ b/j2k/src/org/jetbrains/kotlin/j2k/Converter.kt @@ -72,15 +72,26 @@ class Converter private( private fun createDefaultCodeConverter() = CodeConverter(this, DefaultExpressionConverter(), DefaultStatementConverter(), null) - public fun convert(): ((Map) -> String)? { - val element = convertTopElement(elementToConvert) ?: return null - return { usageProcessings -> - unfoldDeferredElements(usageProcessings) + public data class IntermediateResult( + val codeGenerator: (Map) -> 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) { diff --git a/j2k/src/org/jetbrains/kotlin/j2k/JavaToKotlinConverter.kt b/j2k/src/org/jetbrains/kotlin/j2k/JavaToKotlinConverter.kt index fc7560fa43f..7a689e6c783 100644 --- a/j2k/src/org/jetbrains/kotlin/j2k/JavaToKotlinConverter.kt +++ b/j2k/src/org/jetbrains/kotlin/j2k/JavaToKotlinConverter.kt @@ -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, progress: ProgressIndicator = EmptyProgressIndicator() - ): List { + ): List { try { val elementCount = inputElements.size() - val intermediateResults = ArrayList<((Map) -> String)?>(elementCount) + val intermediateResults = ArrayList<(Converter.IntermediateResult)?>(elementCount) val usageProcessings = HashMap() 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(elementCount) + val results = ArrayList(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(elementCount) + if (postProcessor == null) return results + + val finalResults = ArrayList(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) } } diff --git a/j2k/src/org/jetbrains/kotlin/j2k/JavaToKotlinTranslator.kt b/j2k/src/org/jetbrains/kotlin/j2k/JavaToKotlinTranslator.kt index 742ce63901d..6d12bcc98c4 100644 --- a/j2k/src/org/jetbrains/kotlin/j2k/JavaToKotlinTranslator.kt +++ b/j2k/src/org/jetbrains/kotlin/j2k/JavaToKotlinTranslator.kt @@ -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 "" } diff --git a/j2k/tests/org/jetbrains/kotlin/j2k/AbstractJavaToKotlinConverterMultiFileTest.kt b/j2k/tests/org/jetbrains/kotlin/j2k/AbstractJavaToKotlinConverterMultiFileTest.kt index 78b84d2a2f5..2483d922446 100644 --- a/j2k/tests/org/jetbrains/kotlin/j2k/AbstractJavaToKotlinConverterMultiFileTest.kt +++ b/j2k/tests/org/jetbrains/kotlin/j2k/AbstractJavaToKotlinConverterMultiFileTest.kt @@ -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 = converter.elementsToKotlin(inputElements) + val results: List = converter.elementsToKotlin(inputElements).map { it!!.text } fun expectedFile(i: Int) = File(javaFiles[i].getPath().replace(".java", ".kt")) diff --git a/j2k/tests/org/jetbrains/kotlin/j2k/AbstractJavaToKotlinConverterSingleFileTest.kt b/j2k/tests/org/jetbrains/kotlin/j2k/AbstractJavaToKotlinConverterSingleFileTest.kt index f8a9616c187..f88531a8d79 100644 --- a/j2k/tests/org/jetbrains/kotlin/j2k/AbstractJavaToKotlinConverterSingleFileTest.kt +++ b/j2k/tests/org/jetbrains/kotlin/j2k/AbstractJavaToKotlinConverterSingleFileTest.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 {