diff --git a/idea/src/org/jetbrains/kotlin/idea/actions/JavaToKotlinAction.kt b/idea/src/org/jetbrains/kotlin/idea/actions/JavaToKotlinAction.kt index e749d0ac702..0a7d0cac017 100644 --- a/idea/src/org/jetbrains/kotlin/idea/actions/JavaToKotlinAction.kt +++ b/idea/src/org/jetbrains/kotlin/idea/actions/JavaToKotlinAction.kt @@ -36,7 +36,6 @@ import com.intellij.psi.codeStyle.CodeStyleManager import org.jetbrains.kotlin.idea.j2k.IdeaResolverForConverter import org.jetbrains.kotlin.idea.j2k.J2kPostProcessor import org.jetbrains.kotlin.j2k.ConverterSettings -import org.jetbrains.kotlin.j2k.FilesConversionScope import org.jetbrains.kotlin.j2k.IdeaReferenceSearcher import org.jetbrains.kotlin.j2k.JavaToKotlinConverter import org.jetbrains.kotlin.psi.JetFile @@ -51,7 +50,7 @@ public class JavaToKotlinAction : AnAction() { var convertedTexts: List? = null fun convert() { - val converter = JavaToKotlinConverter(project, ConverterSettings.defaultSettings, FilesConversionScope(javaFiles), IdeaReferenceSearcher, IdeaResolverForConverter) + val converter = JavaToKotlinConverter(project, ConverterSettings.defaultSettings, IdeaReferenceSearcher, IdeaResolverForConverter) convertedTexts = converter.elementsToKotlin(javaFiles.map { it to J2kPostProcessor(it, formatCode = true) }, ProgressManager.getInstance().getProgressIndicator()) } diff --git a/idea/src/org/jetbrains/kotlin/idea/conversion/copy/ConvertJavaCopyPastePostProcessor.kt b/idea/src/org/jetbrains/kotlin/idea/conversion/copy/ConvertJavaCopyPastePostProcessor.kt index 0e52e9be7f7..cec9fe89035 100644 --- a/idea/src/org/jetbrains/kotlin/idea/conversion/copy/ConvertJavaCopyPastePostProcessor.kt +++ b/idea/src/org/jetbrains/kotlin/idea/conversion/copy/ConvertJavaCopyPastePostProcessor.kt @@ -18,25 +18,28 @@ package org.jetbrains.kotlin.idea.conversion.copy import com.intellij.codeInsight.editorActions.CopyPastePostProcessor import com.intellij.codeInsight.editorActions.TextBlockTransferableData +import com.intellij.lang.java.JavaLanguage import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.RangeMarker +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.openapi.util.text.StringUtil import com.intellij.psi.* -import org.jetbrains.kotlin.j2k.* -import org.jetbrains.kotlin.psi.JetFile -import org.jetbrains.kotlin.idea.editor.JetEditorOptions -import java.awt.datatransfer.Transferable -import com.intellij.openapi.util.TextRange import com.intellij.psi.codeStyle.CodeStyleManager -import com.intellij.lang.java.JavaLanguage -import org.jetbrains.kotlin.idea.j2k.J2kPostProcessor +import org.jetbrains.kotlin.idea.editor.JetEditorOptions import org.jetbrains.kotlin.idea.j2k.IdeaResolverForConverter -import com.intellij.openapi.project.DumbService +import org.jetbrains.kotlin.idea.j2k.J2kPostProcessor +import org.jetbrains.kotlin.j2k.ConverterSettings +import org.jetbrains.kotlin.j2k.IdeaReferenceSearcher +import org.jetbrains.kotlin.j2k.JavaToKotlinConverter +import org.jetbrains.kotlin.psi.JetFile import org.jetbrains.kotlin.psi.psiUtil.parents +import java.awt.datatransfer.Transferable +import java.util.ArrayList public class ConvertJavaCopyPastePostProcessor : CopyPastePostProcessor() { private val LOG = Logger.getInstance("#org.jetbrains.kotlin.idea.conversion.copy.ConvertJavaCopyPastePostProcessor") @@ -90,34 +93,55 @@ public class ConvertJavaCopyPastePostProcessor : CopyPastePostProcessor() + for (i in code.startOffsets.indices) { + list.collectElementsToConvert(sourceFile, sourceFileText, TextRange(code.startOffsets[i], code.endOffsets[i])) + } + val converter = JavaToKotlinConverter( sourceFile.getProject(), ConverterSettings.defaultSettings, - FilesConversionScope(listOf(sourceFile)), IdeaReferenceSearcher, IdeaResolverForConverter ) - assert(code.startOffsets.size() == code.endOffsets.size(), "Must have the same size") - val builder = StringBuilder() - for (i in code.startOffsets.indices) { - val textRange = TextRange(code.startOffsets[i], code.endOffsets[i]) - builder.append(convertRangeToKotlin(sourceFile, sourceFileText, targetFile, textRange, converter)) - } - return StringUtil.convertLineSeparators(builder.toString()) + + val results = converter.elementsToKotlin(list.filterIsInstance().map { it to J2kPostProcessor(targetFile, formatCode = false) }) + + var resultIndex = 0 + val text = StringBuilder { + for (o in list) { + if (o is PsiElement) { + val result = results[resultIndex++] + if (!result.isEmpty()) { + append(result) + } + else { // failed to convert element to Kotlin, insert "as is" + append(o.getText()) + } + } + else { + append(o as String) + } + } + }.toString() + + return StringUtil.convertLineSeparators(text) } - private fun convertRangeToKotlin(file: PsiJavaFile, - fileText: String, - targetFile: JetFile, - range: TextRange, - converter: JavaToKotlinConverter): String { - val builder = StringBuilder() + // builds list consisting of PsiElement's to convert and plain String's + private fun MutableList.collectElementsToConvert( + file: PsiJavaFile, + fileText: String, + range: TextRange + ) { var currentRange = range while (!currentRange.isEmpty()) { val leaf = findFirstLeafWhollyInRange(file, currentRange) if (leaf == null) { val unconvertedSuffix = fileText.substring(currentRange.start, currentRange.end) - builder.append(unconvertedSuffix) + add(unconvertedSuffix) break } @@ -130,19 +154,12 @@ public class ConvertJavaCopyPastePostProcessor : CopyPastePostProcessor Boolean, val referenceSearcher: ReferenceSearcher, val resolverForConverter: ResolverForConverter, private val postProcessor: PostProcessor?, @@ -59,17 +59,17 @@ class Converter private( public val specialContext: PsiElement? = personalState.specialContext companion object { - public fun create(elementToConvert: PsiElement, settings: ConverterSettings, conversionScope: ConversionScope, + public fun create(elementToConvert: PsiElement, settings: ConverterSettings, inConversionScope: (PsiElement) -> Boolean, referenceSearcher: ReferenceSearcher, resolverForConverter: ResolverForConverter, postProcessor: PostProcessor?, usageProcessingsCollector: (UsageProcessing) -> Unit): Converter { - return Converter(elementToConvert, settings, conversionScope, referenceSearcher, resolverForConverter, postProcessor, CommonState(usageProcessingsCollector), PersonalState(null)) + return Converter(elementToConvert, settings, inConversionScope, referenceSearcher, resolverForConverter, postProcessor, CommonState(usageProcessingsCollector), PersonalState(null)) } } public fun withSpecialContext(context: PsiElement): Converter = withState(PersonalState(context)) private fun withState(state: PersonalState): Converter - = Converter(elementToConvert, settings, conversionScope, referenceSearcher, resolverForConverter, postProcessor, commonState, state) + = Converter(elementToConvert, settings, inConversionScope, referenceSearcher, resolverForConverter, postProcessor, commonState, state) private fun createDefaultCodeConverter() = CodeConverter(this, DefaultExpressionConverter(), DefaultStatementConverter(), null) @@ -227,7 +227,7 @@ class Converter private( val parameters = annotationMethods .map { method -> val returnType = method.getReturnType() - val typeConverted = if (method == annotationMethods.last && returnType is PsiArrayType) + val typeConverted = if (method == annotationMethods.last() && returnType is PsiArrayType) VarArgType(typeConverter.convertType(returnType.getComponentType(), Nullability.NotNull)) else typeConverter.convertType(returnType, Nullability.NotNull) @@ -377,7 +377,7 @@ class Converter private( var params = convertParameterList(method.getParameterList()) val typeParameterList = convertTypeParameterList(method.getTypeParameterList()) - var body = deferredElement { (codeConverter: CodeConverter) -> + var body = deferredElement { codeConverter: CodeConverter -> val body = codeConverter.withMethodReturnType(method.getReturnType()).convertBlock(method.getBody()) postProcessBody(body) } @@ -410,8 +410,8 @@ class Converter private( if (containing != null) { val hasOtherJavaSuperclasses = containing.getSuperTypes().any { //TODO: correctly check for kotlin class - val `class` = it.resolve() - `class` != null && `class`.getQualifiedName() != JAVA_LANG_OBJECT && !conversionScope.contains(`class`) + val klass = it.resolve() + klass != null && klass.getQualifiedName() != JAVA_LANG_OBJECT && !inConversionScope(klass) } if (hasOtherJavaSuperclasses) return true } diff --git a/j2k/src/org/jetbrains/kotlin/j2k/JavaToKotlinConverter.kt b/j2k/src/org/jetbrains/kotlin/j2k/JavaToKotlinConverter.kt index fff964d14e9..c142327223d 100644 --- a/j2k/src/org/jetbrains/kotlin/j2k/JavaToKotlinConverter.kt +++ b/j2k/src/org/jetbrains/kotlin/j2k/JavaToKotlinConverter.kt @@ -30,18 +30,11 @@ import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.j2k.ast.Element import org.jetbrains.kotlin.j2k.usageProcessing.UsageProcessing import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.isAncestor import org.jetbrains.kotlin.resolve.BindingContext import java.util.ArrayList import java.util.HashMap -public trait ConversionScope { - public fun contains(element: PsiElement): Boolean -} - -public class FilesConversionScope(val files: Collection) : ConversionScope { - override fun contains(element: PsiElement) = files.any { element.getContainingFile() == it } -} - public trait PostProcessor { public val contextToAnalyzeIn: PsiElement public fun analyzeFile(file: JetFile): BindingContext @@ -72,7 +65,6 @@ public trait PostProcessor { public class JavaToKotlinConverter(private val project: Project, private val settings: ConverterSettings, - private val conversionScope: ConversionScope /*TODO: drop this parameter*/, private val referenceSearcher: ReferenceSearcher, private val resolverForConverter: ResolverForConverter) { private val LOG = Logger.getInstance("#org.jetbrains.kotlin.j2k.JavaToKotlinConverter") @@ -124,7 +116,11 @@ public class JavaToKotlinConverter(private val project: Project, processFilesWithProgress(0.25) { i -> val psiElement = psiElementsAndProcessors[i].first val postProcessor = psiElementsAndProcessors[i].second - val converter = Converter.create(psiElement, settings, conversionScope, referenceSearcher, resolverForConverter, postProcessor, usageProcessingCollector) + + fun inConversionScope(element: PsiElement) + = psiElementsAndProcessors.any { it.first.isAncestor(element, strict = false) } + + val converter = Converter.create(psiElement, settings, ::inConversionScope, referenceSearcher, resolverForConverter, postProcessor, usageProcessingCollector) val result = converter.convert() intermediateResults.add(result) } diff --git a/j2k/src/org/jetbrains/kotlin/j2k/JavaToKotlinTranslator.kt b/j2k/src/org/jetbrains/kotlin/j2k/JavaToKotlinTranslator.kt index f00d34b7ae9..0d1e3d1d917 100644 --- a/j2k/src/org/jetbrains/kotlin/j2k/JavaToKotlinTranslator.kt +++ b/j2k/src/org/jetbrains/kotlin/j2k/JavaToKotlinTranslator.kt @@ -134,7 +134,7 @@ public object JavaToKotlinTranslator { fun generateKotlinCode(javaCode: String): String { val file = createFile(javaCode) if (file is PsiJavaFile) { - val converter = JavaToKotlinConverter(file.getProject(), ConverterSettings.defaultSettings, FilesConversionScope(listOf(file)), EmptyReferenceSearcher, EmptyResolverForConverter) + val converter = JavaToKotlinConverter(file.getProject(), ConverterSettings.defaultSettings, EmptyReferenceSearcher, EmptyResolverForConverter) return prettify(converter.elementsToKotlin(listOf(file to null))[0]) } return "" diff --git a/j2k/src/org/jetbrains/kotlin/j2k/TypeConverter.kt b/j2k/src/org/jetbrains/kotlin/j2k/TypeConverter.kt index 1042d7aaac3..c7c463b5ba1 100644 --- a/j2k/src/org/jetbrains/kotlin/j2k/TypeConverter.kt +++ b/j2k/src/org/jetbrains/kotlin/j2k/TypeConverter.kt @@ -151,7 +151,7 @@ class TypeConverter(val converter: Converter) { value = fromTypeHeuristics(variableType) if (value != default) return value - if (!converter.conversionScope.contains(variable)) return default // do not analyze usages of variables not in our conversion scope + if (!converter.inConversionScope(variable)) return default // do not analyze usages of variables not in our conversion scope val scope = searchScope(variable) if (scope != null) { @@ -189,7 +189,7 @@ class TypeConverter(val converter: Converter) { value = fromTypeHeuristics(returnType) if (value != default) return value - if (!converter.conversionScope.contains(method)) return default // do not analyze body and usages of methods out of our conversion scope + if (!converter.inConversionScope(method)) return default // do not analyze body and usages of methods out of our conversion scope val body = method.getBody() if (body != null) { @@ -262,7 +262,7 @@ class TypeConverter(val converter: Converter) { if (variable is PsiField && variable.hasModifierProperty(PsiModifier.PRIVATE) - && converter.conversionScope.contains(variable) + && converter.inConversionScope(variable) && shouldGenerateDefaultInitializer(converter.referenceSearcher, variable)) { return Nullability.Nullable } diff --git a/j2k/tests/org/jetbrains/kotlin/j2k/AbstractJavaToKotlinConverterMultiFileTest.kt b/j2k/tests/org/jetbrains/kotlin/j2k/AbstractJavaToKotlinConverterMultiFileTest.kt index 19eef3c8b7f..68fd4e3df0d 100644 --- a/j2k/tests/org/jetbrains/kotlin/j2k/AbstractJavaToKotlinConverterMultiFileTest.kt +++ b/j2k/tests/org/jetbrains/kotlin/j2k/AbstractJavaToKotlinConverterMultiFileTest.kt @@ -43,7 +43,7 @@ public abstract class AbstractJavaToKotlinConverterMultiFileTest : AbstractJavaT psiFiles.add(psiFile) } - val converter = JavaToKotlinConverter(project, ConverterSettings.defaultSettings, FilesConversionScope(psiFiles), IdeaReferenceSearcher, IdeaResolverForConverter) + val converter = JavaToKotlinConverter(project, ConverterSettings.defaultSettings, IdeaReferenceSearcher, IdeaResolverForConverter) val results: List = converter.elementsToKotlin(psiFiles.map { it to J2kPostProcessor(it, formatCode = true) }) 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 3e3397f2810..3d03e6f76f1 100644 --- a/j2k/tests/org/jetbrains/kotlin/j2k/AbstractJavaToKotlinConverterSingleFileTest.kt +++ b/j2k/tests/org/jetbrains/kotlin/j2k/AbstractJavaToKotlinConverterSingleFileTest.kt @@ -106,7 +106,7 @@ public abstract class AbstractJavaToKotlinConverterSingleFileTest : AbstractJava private fun fileToKotlin(text: String, settings: ConverterSettings, project: Project): String { val file = createJavaFile(text) - val converter = JavaToKotlinConverter(project, settings, FilesConversionScope(listOf(file)), IdeaReferenceSearcher, IdeaResolverForConverter) + val converter = JavaToKotlinConverter(project, settings, IdeaReferenceSearcher, IdeaResolverForConverter) return converter.elementsToKotlin(listOf(file to J2kPostProcessor(file, formatCode = false)))[0] }