From f951ed9dba3a20eae975cc1577918b11fc562c4f Mon Sep 17 00:00:00 2001 From: Ilya Kirillov Date: Wed, 9 Oct 2019 17:19:02 +0300 Subject: [PATCH] New J2K: introduce external code processing for new J2K It will update usages from files not in conversion scope to a valid ones: 1. When converting (field, getter, setter) triple from Java to a Kotlin property, it will: 1. Update every usage of getter/setter in Java/Kotlin code to a usage of actual property 2. If property is used in Java directly then will add @JvmField to it 2. When converting static method or static field without getter and setter, which has Java usages then add @JvmStatic to it #KT-34164 fixed --- .../kotlin/idea/actions/JavaToKotlinAction.kt | 13 +- .../kotlin/j2k/JavaToKotlinConverter.kt | 7 +- .../AccessorToPropertyProcessing.kt | 21 ++-- ...rtGettersAndSettersToPropertyProcessing.kt | 45 ++++++- .../jetbrains/kotlin/nj2k/JKSymbolProvider.kt | 3 +- .../kotlin/nj2k/NewJ2kConverterContext.kt | 4 +- .../kotlin/nj2k/NewJavaToKotlinConverter.kt | 26 +++- .../conversions/FieldToPropertyConversion.kt | 8 ++ .../JavaMethodToKotlinFunctionConversion.kt | 8 ++ .../StaticsToCompanionExtractConversion.kt | 3 + .../kotlin/nj2k/conversions/resolveUtil.kt | 2 +- .../ExternalUsagesFixer.kt | 109 ++++++++++++++++ .../JKExternalConversion.kt | 101 +++++++++++++++ .../externalCodeProcessing/JKMemberData.kt | 79 ++++++++++++ .../NewExternalCodeProcessing.kt | 118 ++++++++++++++++++ .../src/org/jetbrains/kotlin/nj2k/psiUtils.kt | 13 +- 16 files changed, 532 insertions(+), 28 deletions(-) create mode 100644 nj2k/src/org/jetbrains/kotlin/nj2k/externalCodeProcessing/ExternalUsagesFixer.kt create mode 100644 nj2k/src/org/jetbrains/kotlin/nj2k/externalCodeProcessing/JKExternalConversion.kt create mode 100644 nj2k/src/org/jetbrains/kotlin/nj2k/externalCodeProcessing/JKMemberData.kt create mode 100644 nj2k/src/org/jetbrains/kotlin/nj2k/externalCodeProcessing/NewExternalCodeProcessing.kt diff --git a/idea/src/org/jetbrains/kotlin/idea/actions/JavaToKotlinAction.kt b/idea/src/org/jetbrains/kotlin/idea/actions/JavaToKotlinAction.kt index 714f2e455ac..850dce16207 100644 --- a/idea/src/org/jetbrains/kotlin/idea/actions/JavaToKotlinAction.kt +++ b/idea/src/org/jetbrains/kotlin/idea/actions/JavaToKotlinAction.kt @@ -48,6 +48,7 @@ import com.intellij.psi.PsiManager import com.intellij.psi.util.PsiTreeUtil import org.jetbrains.kotlin.idea.KotlinFileType import org.jetbrains.kotlin.idea.core.util.toPsiFile +import org.jetbrains.kotlin.idea.formatter.commitAndUnblockDocument import org.jetbrains.kotlin.idea.j2k.IdeaJavaToKotlinServices import org.jetbrains.kotlin.idea.j2k.J2kPostProcessor import org.jetbrains.kotlin.idea.util.application.executeWriteCommand @@ -56,6 +57,7 @@ import org.jetbrains.kotlin.idea.util.isRunningInCidrIde import org.jetbrains.kotlin.j2k.* import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.UserDataProperty +import org.jetbrains.kotlin.utils.addToStdlib.safeAs import java.io.File import java.io.IOException import java.util.* @@ -105,6 +107,7 @@ class JavaToKotlinAction : AnAction() { virtualFile.pathBeforeJ2K = virtualFile.path virtualFile.rename(this, fileName) } + result += virtualFile } catch (e: IOException) { MessagesEx.error(psiFile.project, e.message ?: "").showLater() } @@ -165,7 +168,7 @@ class JavaToKotlinAction : AnAction() { ) return emptyList() - var externalCodeUpdate: (() -> Unit)? = null + var externalCodeUpdate: ((List) -> Unit)? = null if (enableExternalCodeProcessing && converterResult!!.externalCodeProcessing != null) { val question = @@ -196,16 +199,18 @@ class JavaToKotlinAction : AnAction() { CommandProcessor.getInstance().markCurrentCommandAsGlobal(project) val newFiles = saveResults(javaFiles, converterResult!!.results) + .map { it.toPsiFile(project) as KtFile } + .onEach { it.commitAndUnblockDocument() } - externalCodeUpdate?.invoke() + externalCodeUpdate?.invoke(newFiles) PsiDocumentManager.getInstance(project).commitAllDocuments() newFiles.singleOrNull()?.let { - FileEditorManager.getInstance(project).openFile(it, true) + FileEditorManager.getInstance(project).openFile(it.virtualFile, true) } - newFiles.map { it.toPsiFile(project) as KtFile } + newFiles } } } diff --git a/j2k/src/org/jetbrains/kotlin/j2k/JavaToKotlinConverter.kt b/j2k/src/org/jetbrains/kotlin/j2k/JavaToKotlinConverter.kt index 51ae114042d..1d1f9ab9596 100644 --- a/j2k/src/org/jetbrains/kotlin/j2k/JavaToKotlinConverter.kt +++ b/j2k/src/org/jetbrains/kotlin/j2k/JavaToKotlinConverter.kt @@ -56,7 +56,7 @@ enum class ParseContext { } interface ExternalCodeProcessing { - fun prepareWriteOperation(progress: ProgressIndicator): () -> Unit + fun prepareWriteOperation(progress: ProgressIndicator?): (List) -> Unit } @@ -190,7 +190,8 @@ class OldJavaToKotlinConverter( if (map.isEmpty()) return null return object : ExternalCodeProcessing { - override fun prepareWriteOperation(progress: ProgressIndicator): () -> Unit { + override fun prepareWriteOperation(progress: ProgressIndicator?): (List) -> Unit { + if (progress == null) error("Progress should not be null for old J2K") val refs = ArrayList() progress.text = "Searching usages to update..." @@ -322,7 +323,7 @@ class OldWithProgressProcessor(private val progress: ProgressIndicator?, private } } -private class ProgressPortionReporter( +class ProgressPortionReporter( indicator: ProgressIndicator, private val start: Double, private val portion: Double diff --git a/j2k/src/org/jetbrains/kotlin/j2k/usageProcessing/AccessorToPropertyProcessing.kt b/j2k/src/org/jetbrains/kotlin/j2k/usageProcessing/AccessorToPropertyProcessing.kt index cf28bd773ff..7f70fafcca5 100644 --- a/j2k/src/org/jetbrains/kotlin/j2k/usageProcessing/AccessorToPropertyProcessing.kt +++ b/j2k/src/org/jetbrains/kotlin/j2k/usageProcessing/AccessorToPropertyProcessing.kt @@ -58,11 +58,20 @@ class AccessorToPropertyProcessing(val accessorMethod: PsiMethod, val accessorKi if (accessorMethod.hasModifierProperty(PsiModifier.PRIVATE)) emptyList() else - listOf(AccessorToPropertyProcessor()) + listOf(AccessorToPropertyProcessor(propertyName, accessorKind)) - inner class AccessorToPropertyProcessor: ExternalCodeProcessor { + class AccessorToPropertyProcessor( + private val propertyName: String, + private val accessorKind: AccessorKind + ) : ExternalCodeProcessor { override fun processUsage(reference: PsiReference): Array? { - val nameExpr = reference.element as? KtSimpleNameExpression ?: return null + return processUsage(reference.element, propertyName, accessorKind) + } + } + + companion object { + fun processUsage(element: PsiElement, propertyName: String, accessorKind: AccessorKind): Array? { + val nameExpr = element as? KtSimpleNameExpression ?: return null val callExpr = nameExpr.parent as? KtCallExpression ?: return null val arguments = callExpr.valueArguments @@ -73,8 +82,7 @@ class AccessorToPropertyProcessing(val accessorMethod: PsiMethod, val accessorKi if (arguments.size != 0) return null // incorrect call propertyNameExpr = callExpr.replace(propertyNameExpr) as KtSimpleNameExpression return propertyNameExpr.references - } - else { + } else { val value = arguments.singleOrNull()?.getArgumentExpression() ?: return null var assignment = factory.createExpression("a = b") as KtBinaryExpression assignment.right!!.replace(value) @@ -85,8 +93,7 @@ class AccessorToPropertyProcessing(val accessorMethod: PsiMethod, val accessorKi assignment.left!!.replace(qualifiedExpression) assignment = qualifiedExpression.replace(assignment) as KtBinaryExpression (assignment.left as KtQualifiedExpression).selectorExpression!!.references - } - else { + } else { assignment.left!!.replace(propertyNameExpr) assignment = callExpr.replace(assignment) as KtBinaryExpression assignment.left!!.references diff --git a/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/postProcessing/processings/ConvertGettersAndSettersToPropertyProcessing.kt b/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/postProcessing/processings/ConvertGettersAndSettersToPropertyProcessing.kt index b68c70cce6e..980ad385ad4 100644 --- a/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/postProcessing/processings/ConvertGettersAndSettersToPropertyProcessing.kt +++ b/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/postProcessing/processings/ConvertGettersAndSettersToPropertyProcessing.kt @@ -27,9 +27,12 @@ import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi import org.jetbrains.kotlin.lexer.KtTokens -import org.jetbrains.kotlin.nj2k.NewJ2kConverterContext -import org.jetbrains.kotlin.nj2k.asGetterName -import org.jetbrains.kotlin.nj2k.asSetterName +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.nj2k.* +import org.jetbrains.kotlin.nj2k.externalCodeProcessing.JKFakeFieldData +import org.jetbrains.kotlin.nj2k.externalCodeProcessing.JKFieldData +import org.jetbrains.kotlin.nj2k.externalCodeProcessing.JKMethodData +import org.jetbrains.kotlin.nj2k.externalCodeProcessing.NewExternalCodeProcessing import org.jetbrains.kotlin.nj2k.postProcessing.* import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.* @@ -284,7 +287,7 @@ class ConvertGettersAndSettersToPropertyProcessing : ElementsBasedPostProcessing if (classesWithPropertiesData.isEmpty()) return runUndoTransparentActionInEdt(inWriteAction = true) { for ((klass, propertiesData) in classesWithPropertiesData) { - convertClass(klass, propertiesData) + convertClass(klass, propertiesData, converterContext.externalCodeProcessor) } } } @@ -535,7 +538,11 @@ class ConvertGettersAndSettersToPropertyProcessing : ElementsBasedPostProcessing setName(newName) } - private fun convertClass(klass: KtClassOrObject, propertiesData: List) { + private fun convertClass( + klass: KtClassOrObject, + propertiesData: List, + externalCodeUpdater: NewExternalCodeProcessing + ) { val factory = KtPsiFactory(klass) val accessors = propertiesData.filterGettersAndSetters(klass, factory) @@ -558,6 +565,34 @@ class ConvertGettersAndSettersToPropertyProcessing : ElementsBasedPostProcessing } } + + val propertyInfo = when (property) { + is RealProperty -> property.property.fqNameWithoutCompanions.let(externalCodeUpdater::getMember) + is MergedProperty -> property.mergeTo.fqNameWithoutCompanions.let(externalCodeUpdater::getMember) + is FakeProperty -> JKFakeFieldData( + isStatic = klass is KtObjectDeclaration, + kotlinElementPointer = null, + fqName = klass.fqNameWithoutCompanions.child(Name.identifier(property.name)), + name = property.name + ).also { externalCodeUpdater.addMember(it) } + }?.also { it.name = property.name } as? JKFieldData + + + val getterFqName = getter.safeAs()?.function?.fqNameWithoutCompanions + val setterFqName = setter.safeAs()?.function?.fqNameWithoutCompanions + + getterFqName?.let { fqName -> + externalCodeUpdater.getMember(fqName)?.safeAs()?.let { + it.usedAsAccessorOfProperty = propertyInfo ?: return@let + } + } + + setterFqName?.let { fqName -> + externalCodeUpdater.getMember(fqName)?.safeAs()?.let { + it.usedAsAccessorOfProperty = propertyInfo ?: return@let + } + } + val isOpen = getter.safeAs()?.function?.hasModifier(KtTokens.OPEN_KEYWORD) == true || setter.safeAs()?.function?.hasModifier(KtTokens.OPEN_KEYWORD) == true diff --git a/nj2k/src/org/jetbrains/kotlin/nj2k/JKSymbolProvider.kt b/nj2k/src/org/jetbrains/kotlin/nj2k/JKSymbolProvider.kt index 169016421ae..c58337bf621 100644 --- a/nj2k/src/org/jetbrains/kotlin/nj2k/JKSymbolProvider.kt +++ b/nj2k/src/org/jetbrains/kotlin/nj2k/JKSymbolProvider.kt @@ -18,11 +18,10 @@ import org.jetbrains.kotlin.nj2k.types.JKTypeFactory import org.jetbrains.kotlin.psi.* -class JKSymbolProvider(project: Project, module: Module?, contextElement: PsiElement) { +class JKSymbolProvider(private val resolver: JKResolver) { private val symbolsByFqName = mutableMapOf() val symbolsByPsi = mutableMapOf() private val symbolsByJK = mutableMapOf() - private val resolver = JKResolver(project, module, contextElement) private val elementVisitor = ElementVisitor() lateinit var typeFactory: JKTypeFactory diff --git a/nj2k/src/org/jetbrains/kotlin/nj2k/NewJ2kConverterContext.kt b/nj2k/src/org/jetbrains/kotlin/nj2k/NewJ2kConverterContext.kt index 63669a7bf1a..f9f3f80431c 100644 --- a/nj2k/src/org/jetbrains/kotlin/nj2k/NewJ2kConverterContext.kt +++ b/nj2k/src/org/jetbrains/kotlin/nj2k/NewJ2kConverterContext.kt @@ -8,6 +8,7 @@ package org.jetbrains.kotlin.nj2k import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement import org.jetbrains.kotlin.j2k.* +import org.jetbrains.kotlin.nj2k.externalCodeProcessing.NewExternalCodeProcessing import org.jetbrains.kotlin.nj2k.types.JKTypeFactory data class NewJ2kConverterContext( @@ -16,7 +17,8 @@ data class NewJ2kConverterContext( val converter: NewJavaToKotlinConverter, val inConversionContext: (PsiElement) -> Boolean, val importStorage: JKImportStorage, - val elementsInfoStorage: JKElementInfoStorage + val elementsInfoStorage: JKElementInfoStorage, + val externalCodeProcessor: NewExternalCodeProcessing ) : ConverterContext { val project: Project get() = converter.project diff --git a/nj2k/src/org/jetbrains/kotlin/nj2k/NewJavaToKotlinConverter.kt b/nj2k/src/org/jetbrains/kotlin/nj2k/NewJavaToKotlinConverter.kt index fff888fd532..e2be8e94671 100644 --- a/nj2k/src/org/jetbrains/kotlin/nj2k/NewJavaToKotlinConverter.kt +++ b/nj2k/src/org/jetbrains/kotlin/nj2k/NewJavaToKotlinConverter.kt @@ -31,12 +31,15 @@ import org.jetbrains.kotlin.idea.project.languageVersionSettings import org.jetbrains.kotlin.idea.util.application.runWriteAction import org.jetbrains.kotlin.j2k.* import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.nj2k.conversions.JKResolver +import org.jetbrains.kotlin.nj2k.externalCodeProcessing.NewExternalCodeProcessing import org.jetbrains.kotlin.nj2k.printing.JKCodeBuilder import org.jetbrains.kotlin.nj2k.types.JKTypeFactory import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtImportList import org.jetbrains.kotlin.psi.KtPsiFactory +import org.jetbrains.kotlin.psi.psiUtil.isAncestor import org.jetbrains.kotlin.resolve.ImportPath class NewJavaToKotlinConverter( @@ -128,7 +131,8 @@ class NewJavaToKotlinConverter( override fun elementsToKotlin(inputElements: List, processor: WithProgressProcessor): Result { val phaseDescription = "Converting Java code to Kotlin code" val contextElement = inputElements.firstOrNull() ?: return Result(emptyList(), null, null) - val symbolProvider = JKSymbolProvider(project, targetModule, contextElement) + val resolver = JKResolver(project, targetModule, contextElement) + val symbolProvider = JKSymbolProvider(resolver) val typeFactory = JKTypeFactory(symbolProvider) symbolProvider.typeFactory = typeFactory symbolProvider.preBuildTree(inputElements) @@ -152,14 +156,24 @@ class NewJavaToKotlinConverter( processor.updateState(i, 1, phaseDescription) element to treeBuilder.buildTree(element, saveImports) } + val inConversionContext = { element: PsiElement -> + inputElements.any { inputElement -> + if (inputElement == element) return@any true + inputElement.isAncestor(element, true) + } + } + + val externalCodeProcessing = + NewExternalCodeProcessing(oldConverterServices.referenceSearcher, inConversionContext) val context = NewJ2kConverterContext( symbolProvider, typeFactory, this, - { it.containingFile in inputElements }, + inConversionContext, importStorage, - JKElementInfoStorage() + JKElementInfoStorage(), + externalCodeProcessing ) ConversionsRunner.doApply(asts.withIndex().mapNotNull { (i, ast) -> processor.updateState(i, 1, phaseDescription) @@ -182,7 +196,11 @@ class NewJavaToKotlinConverter( ) } - return Result(results, null, context) + return Result( + results, + externalCodeProcessing.takeIf { it.isExternalProcessingNeeded() }, + context + ) } override fun elementsToKotlin(inputElements: List): Result { diff --git a/nj2k/src/org/jetbrains/kotlin/nj2k/conversions/FieldToPropertyConversion.kt b/nj2k/src/org/jetbrains/kotlin/nj2k/conversions/FieldToPropertyConversion.kt index 383dac94fb6..13010bd1525 100644 --- a/nj2k/src/org/jetbrains/kotlin/nj2k/conversions/FieldToPropertyConversion.kt +++ b/nj2k/src/org/jetbrains/kotlin/nj2k/conversions/FieldToPropertyConversion.kt @@ -5,7 +5,10 @@ package org.jetbrains.kotlin.nj2k.conversions +import com.intellij.psi.PsiField +import org.jetbrains.kotlin.nj2k.externalCodeProcessing.JKFieldDataFromJava import org.jetbrains.kotlin.nj2k.NewJ2kConverterContext +import org.jetbrains.kotlin.nj2k.psi import org.jetbrains.kotlin.nj2k.tree.* @@ -13,6 +16,11 @@ class FieldToPropertyConversion(context: NewJ2kConverterContext) : RecursiveAppl override fun applyToElement(element: JKTreeElement): JKTreeElement { if (element !is JKField) return recurse(element) element.mutability = if (element.modality == Modality.FINAL) Mutability.IMMUTABLE else Mutability.MUTABLE + + element.psi()?.let { psi -> + context.externalCodeProcessor.addMember(JKFieldDataFromJava(psi)) + } + element.modality = Modality.FINAL return recurse(element) } diff --git a/nj2k/src/org/jetbrains/kotlin/nj2k/conversions/JavaMethodToKotlinFunctionConversion.kt b/nj2k/src/org/jetbrains/kotlin/nj2k/conversions/JavaMethodToKotlinFunctionConversion.kt index a1c2c431572..8732e021805 100644 --- a/nj2k/src/org/jetbrains/kotlin/nj2k/conversions/JavaMethodToKotlinFunctionConversion.kt +++ b/nj2k/src/org/jetbrains/kotlin/nj2k/conversions/JavaMethodToKotlinFunctionConversion.kt @@ -16,8 +16,11 @@ package org.jetbrains.kotlin.nj2k.conversions +import com.intellij.psi.PsiMethod import org.jetbrains.kotlin.j2k.ast.Nullability +import org.jetbrains.kotlin.nj2k.externalCodeProcessing.JKMethodData import org.jetbrains.kotlin.nj2k.NewJ2kConverterContext +import org.jetbrains.kotlin.nj2k.psi import org.jetbrains.kotlin.nj2k.throwAnnotation import org.jetbrains.kotlin.nj2k.tree.JKMethodImpl import org.jetbrains.kotlin.nj2k.tree.JKTreeElement @@ -34,6 +37,11 @@ class JavaMethodToKotlinFunctionConversion(context: NewJ2kConverterContext) : Re symbolProvider ) } + + element.psi()?.let { psi -> + context.externalCodeProcessor.addMember(JKMethodData(psi)) + } + return recurse(element) } } \ No newline at end of file diff --git a/nj2k/src/org/jetbrains/kotlin/nj2k/conversions/StaticsToCompanionExtractConversion.kt b/nj2k/src/org/jetbrains/kotlin/nj2k/conversions/StaticsToCompanionExtractConversion.kt index 0084cf8d571..2c3eb5f8753 100644 --- a/nj2k/src/org/jetbrains/kotlin/nj2k/conversions/StaticsToCompanionExtractConversion.kt +++ b/nj2k/src/org/jetbrains/kotlin/nj2k/conversions/StaticsToCompanionExtractConversion.kt @@ -26,6 +26,9 @@ class StaticsToCompanionExtractConversion(context: NewJ2kConverterContext) : Rec if (declaration is JKOtherModifiersOwner) { declaration.otherModifierElements -= declaration.elementByModifier(OtherModifier.STATIC)!! } + context.externalCodeProcessor.getMember(declaration)?.let { + it.isStatic = true + } } return recurse(element) } diff --git a/nj2k/src/org/jetbrains/kotlin/nj2k/conversions/resolveUtil.kt b/nj2k/src/org/jetbrains/kotlin/nj2k/conversions/resolveUtil.kt index 253d45bccda..45c392fc4e1 100644 --- a/nj2k/src/org/jetbrains/kotlin/nj2k/conversions/resolveUtil.kt +++ b/nj2k/src/org/jetbrains/kotlin/nj2k/conversions/resolveUtil.kt @@ -22,7 +22,7 @@ import org.jetbrains.kotlin.psi.psiUtil.getChildOfType import org.jetbrains.kotlin.resolve.ImportPath -class JKResolver(private val project: Project, module: Module?, private val contextElement: PsiElement) { +class JKResolver(val project: Project, module: Module?, private val contextElement: PsiElement) { private val scope = module?.let { GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(it) } ?: GlobalSearchScope.allScope(project) diff --git a/nj2k/src/org/jetbrains/kotlin/nj2k/externalCodeProcessing/ExternalUsagesFixer.kt b/nj2k/src/org/jetbrains/kotlin/nj2k/externalCodeProcessing/ExternalUsagesFixer.kt new file mode 100644 index 00000000000..6b82e9d25da --- /dev/null +++ b/nj2k/src/org/jetbrains/kotlin/nj2k/externalCodeProcessing/ExternalUsagesFixer.kt @@ -0,0 +1,109 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.nj2k.externalCodeProcessing + +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.j2k.AccessorKind +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.load.java.JvmAbi +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.psi.KtElement +import org.jetbrains.kotlin.psi.KtProperty +import org.jetbrains.kotlin.psi.KtPsiFactory + +internal class ExternalUsagesFixer(private val usages: List) { + private val conversions = mutableListOf() + + fun fix() { + usages.forEach { it.fix() } + conversions.sort() + conversions.forEach(JKExternalConversion::apply) + } + + private fun JKMemberInfoWithUsages.fix() { + when (member) { + is JKFieldDataFromJava -> member.fix(javaUsages, kotlinUsages) + is JKMethodData -> member.fix(javaUsages, kotlinUsages) + } + } + + private fun JKFieldDataFromJava.fix(javaUsages: List, kotlinUsages: List) { + run { + val ktProperty = kotlinElement ?: return@run + when { + javaUsages.isNotEmpty() && ktProperty.isSimpleProperty() -> + ktProperty.addAnnotationIfThereAreNoJvmOnes(JvmAbi.JVM_FIELD_ANNOTATION_FQ_NAME) + + javaUsages.isNotEmpty() && isStatic && !ktProperty.hasModifier(KtTokens.CONST_KEYWORD) -> + ktProperty.addAnnotationIfThereAreNoJvmOnes(JVM_STATIC_FQ_NAME) + } + } + + if (wasRenamed) { + javaUsages.forEach { usage -> + conversions += PropertyRenamedJavaExternalUsageConversion(name, usage) + } + + kotlinUsages.forEach { usage -> + conversions += PropertyRenamedKotlinExternalUsageConversion(name, usage) + } + } + } + + private fun JKMethodData.fix(javaUsages: List, kotlinUsages: List) { + usedAsAccessorOfProperty?.let { property -> + val ktProperty = property.kotlinElement + val accessorKind = + if (javaElement.name.startsWith("set")) AccessorKind.SETTER + else AccessorKind.GETTER + + kotlinUsages.forEach { usage -> + conversions += AccessorToPropertyKotlinExternalConversion(property.name, accessorKind, usage) + } + + if (ktProperty != null + && javaUsages.isNotEmpty() + && ktProperty.isSimpleProperty() + ) javaUsages.forEach { usage -> + conversions += AccessorToPropertyJavaExternalConversion(property.name, accessorKind, usage) + } + } + if (javaUsages.isNotEmpty() && isStatic) { + when (val accessorOf = usedAsAccessorOfProperty) { + null -> this + else -> accessorOf + }.kotlinElement?.addAnnotationIfThereAreNoJvmOnes(JVM_STATIC_FQ_NAME) + } + } + + private fun KtProperty.isSimpleProperty() = + getter == null + && setter == null + && !hasModifier(KtTokens.CONST_KEYWORD) + + private fun KtDeclaration.addAnnotationIfThereAreNoJvmOnes(fqName: FqName) { + // we don't want to resolve here and as we are working with fqNames, just by-text comparing is OK + if (annotationEntries.any { entry -> + USED_JVM_ANNOTATIONS.any { jvmAnnotation -> + entry.typeReference?.textMatches(jvmAnnotation.asString()) == true + } + } + ) return + addAnnotationEntry(KtPsiFactory(this).createAnnotationEntry("@${fqName.asString()}")) + } + + internal data class JKMemberInfoWithUsages( + val member: JKMemberData<*>, + val javaUsages: List, + val kotlinUsages: List + ) + + companion object { + private val JVM_STATIC_FQ_NAME = FqName("kotlin.jvm.JvmStatic") + val USED_JVM_ANNOTATIONS = listOf(JVM_STATIC_FQ_NAME, JvmAbi.JVM_FIELD_ANNOTATION_FQ_NAME) + } +} \ No newline at end of file diff --git a/nj2k/src/org/jetbrains/kotlin/nj2k/externalCodeProcessing/JKExternalConversion.kt b/nj2k/src/org/jetbrains/kotlin/nj2k/externalCodeProcessing/JKExternalConversion.kt new file mode 100644 index 00000000000..925163410a9 --- /dev/null +++ b/nj2k/src/org/jetbrains/kotlin/nj2k/externalCodeProcessing/JKExternalConversion.kt @@ -0,0 +1,101 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.nj2k.externalCodeProcessing + +import com.intellij.psi.* +import org.jetbrains.kotlin.j2k.AccessorKind +import org.jetbrains.kotlin.j2k.usageProcessing.AccessorToPropertyProcessing +import org.jetbrains.kotlin.psi.KtElement +import org.jetbrains.kotlin.psi.KtPsiFactory +import org.jetbrains.kotlin.psi.KtSimpleNameExpression +import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf +import org.jetbrains.kotlin.utils.addToStdlib.cast + +internal sealed class JKExternalConversion : Comparable { + abstract val usage: PsiElement + + abstract fun apply() + + private val depth by lazy(LazyThreadSafetyMode.NONE) { usage.parentsWithSelf.takeWhile { it !is PsiFile }.count() } + private val offset by lazy(LazyThreadSafetyMode.NONE) { usage.textRange.startOffset } + + override fun compareTo(other: JKExternalConversion): Int { + val depth1 = depth + val depth2 = other.depth + if (depth1 != depth2) { // put deeper elements first to not invalidate them when processing ancestors + return -depth1.compareTo(depth2) + } + + // process elements with the same deepness from right to left + // so that right-side of assignments is not invalidated by processing of the left one + return -offset.compareTo(other.offset) + } +} + +internal class AccessorToPropertyKotlinExternalConversion( + private val name: String, + private val accessorKind: AccessorKind, + override val usage: PsiElement +) : JKExternalConversion() { + override fun apply() { + AccessorToPropertyProcessing.processUsage(usage, name, accessorKind) + } +} + +internal class AccessorToPropertyJavaExternalConversion( + private val name: String, + private val accessorKind: AccessorKind, + override val usage: PsiElement +) : JKExternalConversion() { + override fun apply() { + if (usage !is PsiReferenceExpression) return + val methodCall = usage.parent as? PsiMethodCallExpression ?: return + + val factory = PsiElementFactory.SERVICE.getInstance(usage.project) + val propertyAccess = factory.createReferenceExpression(usage.qualifierExpression) + val newExpression = when (accessorKind) { + AccessorKind.GETTER -> propertyAccess + AccessorKind.SETTER -> { + val value = methodCall.argumentList.expressions.singleOrNull() ?: return + factory.createAssignment(propertyAccess, value) + } + } + methodCall.replace(newExpression) + } + + private fun PsiElementFactory.createReferenceExpression(qualifier: PsiExpression?): PsiReferenceExpression = + createExpressionFromText(qualifier?.let { "qualifier." }.orEmpty() + name, usage).cast().apply { + qualifierExpression?.replace(qualifier ?: return@apply) + } + + private fun PsiElementFactory.createAssignment(target: PsiExpression, value: PsiExpression): PsiAssignmentExpression = + createExpressionFromText("x = 1", usage).cast().apply { + lExpression.replace(target) + rExpression!!.replace(value) + } +} + +internal class PropertyRenamedKotlinExternalUsageConversion( + private val newName: String, + override val usage: KtElement +) : JKExternalConversion() { + override fun apply() { + if (usage !is KtSimpleNameExpression) return + val factory = KtPsiFactory(usage) + usage.getReferencedNameElement().replace(factory.createExpression(newName)) + } +} + +internal class PropertyRenamedJavaExternalUsageConversion( + private val newName: String, + override val usage: PsiElement +) : JKExternalConversion() { + override fun apply() { + if (usage !is PsiReferenceExpression) return + val factory = PsiElementFactory.SERVICE.getInstance(usage.project) + usage.referenceNameElement?.replace(factory.createExpressionFromText(newName, usage)) + } +} diff --git a/nj2k/src/org/jetbrains/kotlin/nj2k/externalCodeProcessing/JKMemberData.kt b/nj2k/src/org/jetbrains/kotlin/nj2k/externalCodeProcessing/JKMemberData.kt new file mode 100644 index 00000000000..246438b1006 --- /dev/null +++ b/nj2k/src/org/jetbrains/kotlin/nj2k/externalCodeProcessing/JKMemberData.kt @@ -0,0 +1,79 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.nj2k.externalCodeProcessing + +import com.intellij.psi.PsiField +import com.intellij.psi.PsiMember +import com.intellij.psi.PsiMethod +import com.intellij.psi.SmartPsiElementPointer +import org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.psi.KtNamedFunction +import org.jetbrains.kotlin.psi.KtProperty +import org.jetbrains.kotlin.psi.psiUtil.isPrivate + +interface JKMemberData { + var kotlinElementPointer: SmartPsiElementPointer? + var isStatic: Boolean + val fqName: FqName? + var name: String + + val kotlinElement + get() = kotlinElementPointer?.element + + val searchInJavaFiles: Boolean + get() = true + val searchInKotlinFiles: Boolean + get() = true + + val searchingNeeded + get() = kotlinElement?.isPrivate() != true && (searchInJavaFiles || searchInKotlinFiles) +} + +interface JKMemberDataCameFromJava : JKMemberData { + val javaElement: J + + override val fqName + get() = javaElement.getKotlinFqName() +} + + +interface JKFieldData : JKMemberData + +data class JKFakeFieldData( + override var isStatic: Boolean, + override var kotlinElementPointer: SmartPsiElementPointer? = null, + override val fqName: FqName?, + override var name: String +) : JKFieldData { + override val searchInJavaFiles: Boolean + get() = false + override val searchInKotlinFiles: Boolean + get() = false +} + +data class JKFieldDataFromJava( + override val javaElement: PsiField, + override var isStatic: Boolean = false, + override var kotlinElementPointer: SmartPsiElementPointer? = null, + override var name: String = javaElement.name +) : JKMemberDataCameFromJava, JKFieldData { + override val searchInKotlinFiles: Boolean + get() = wasRenamed + + val wasRenamed: Boolean + get() = javaElement.name != name +} + +data class JKMethodData( + override val javaElement: PsiMethod, + override var isStatic: Boolean = false, + override var kotlinElementPointer: SmartPsiElementPointer? = null, + var usedAsAccessorOfProperty: JKFieldData? = null +) : JKMemberDataCameFromJava { + override var name: String = javaElement.name +} \ No newline at end of file diff --git a/nj2k/src/org/jetbrains/kotlin/nj2k/externalCodeProcessing/NewExternalCodeProcessing.kt b/nj2k/src/org/jetbrains/kotlin/nj2k/externalCodeProcessing/NewExternalCodeProcessing.kt new file mode 100644 index 00000000000..e2f85efb04d --- /dev/null +++ b/nj2k/src/org/jetbrains/kotlin/nj2k/externalCodeProcessing/NewExternalCodeProcessing.kt @@ -0,0 +1,118 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.nj2k.externalCodeProcessing + +import com.intellij.lang.java.JavaLanguage +import com.intellij.openapi.progress.ProgressIndicator +import com.intellij.openapi.progress.ProgressManager +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiMember +import com.intellij.psi.SmartPointerManager +import org.jetbrains.kotlin.idea.core.ShortenReferences +import org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName +import org.jetbrains.kotlin.j2k.ExternalCodeProcessing +import org.jetbrains.kotlin.j2k.ProgressPortionReporter +import org.jetbrains.kotlin.j2k.ReferenceSearcher +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.nj2k.fqNameWithoutCompanions +import org.jetbrains.kotlin.nj2k.psi +import org.jetbrains.kotlin.nj2k.tree.JKDeclaration +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.forEachDescendantOfType + + +class NewExternalCodeProcessing( + private val referenceSearcher: ReferenceSearcher, + private val inConversionContext: (PsiElement) -> Boolean +) : ExternalCodeProcessing { + private val members = mutableMapOf>() + + fun addMember(data: JKMemberData<*>) { + members[data.fqName ?: return] = data + } + + fun getMember(element: JKDeclaration) = + element.psi()?.getKotlinFqName()?.let(members::get) + + fun getMember(fqName: FqName) = + members[fqName] + + fun isExternalProcessingNeeded(): Boolean = + members.values.any { it.searchingNeeded } + + private fun List.bindJavaDeclarationsToConvertedKotlinOnes() { + forEach { file -> + file.forEachDescendantOfType { declaration -> + val member = getMember(declaration.fqNameWithoutCompanions) ?: return@forEachDescendantOfType + when { + member is JKFieldData && declaration is KtProperty -> + member.kotlinElementPointer = SmartPointerManager.createPointer(declaration) + member is JKMethodData && declaration is KtNamedFunction -> + member.kotlinElementPointer = SmartPointerManager.createPointer(declaration) + } + } + } + } + + private fun List.shortenJvmAnnotationsFqNames() { + val filter = filter@{ element: PsiElement -> + if (element !is KtUserType) return@filter ShortenReferences.FilterResult.GO_INSIDE + val isJvmAnnotation = ExternalUsagesFixer.USED_JVM_ANNOTATIONS.any { annotation -> + element.textMatches(annotation.asString()) + } + if (isJvmAnnotation) ShortenReferences.FilterResult.PROCESS + else ShortenReferences.FilterResult.SKIP + } + for (file in this) { + ShortenReferences.DEFAULT.process(file, filter) + } + } + + override fun prepareWriteOperation(progress: ProgressIndicator?): (List) -> Unit { + progress?.text = "Searching usages to update..." + + val usages = mutableListOf() + for ((index, member) in members.values.withIndex()) { + if (progress != null) { + progress.text2 = member.fqName?.shortName()?.identifier ?: continue + progress.checkCanceled() + + ProgressManager.getInstance().runProcess( + { usages += member.collectUsages() }, + ProgressPortionReporter(progress, index / members.size.toDouble(), 1.0 / members.size) + ) + } else { + usages += member.collectUsages() + } + } + return { files -> + files.bindJavaDeclarationsToConvertedKotlinOnes() + ExternalUsagesFixer(usages).fix() + files.shortenJvmAnnotationsFqNames() + } + } + + + private fun JKMemberData<*>.collectUsages(): ExternalUsagesFixer.JKMemberInfoWithUsages { + val javaUsages = mutableListOf() + val kotlinUsages = mutableListOf() + if (this is JKMemberDataCameFromJava<*, *>) referenceSearcher.findUsagesForExternalCodeProcessing( + javaElement, + searchJava = searchInJavaFiles, + searchKotlin = searchInKotlinFiles + ).forEach { usage -> + val element = usage.element + if (inConversionContext(element)) return@forEach + when { + element is KtElement -> kotlinUsages += element + element.language == JavaLanguage.INSTANCE -> javaUsages += element + } + } + return ExternalUsagesFixer.JKMemberInfoWithUsages(this, javaUsages, kotlinUsages) + } +} + + diff --git a/nj2k/src/org/jetbrains/kotlin/nj2k/psiUtils.kt b/nj2k/src/org/jetbrains/kotlin/nj2k/psiUtils.kt index 3ae480e6195..b728d99153f 100644 --- a/nj2k/src/org/jetbrains/kotlin/nj2k/psiUtils.kt +++ b/nj2k/src/org/jetbrains/kotlin/nj2k/psiUtils.kt @@ -15,10 +15,15 @@ import com.intellij.psi.util.PsiUtil import org.jetbrains.kotlin.j2k.ClassKind import org.jetbrains.kotlin.j2k.ReferenceSearcher import org.jetbrains.kotlin.j2k.isNullLiteral +import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.nj2k.tree.* +import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.psi.KtObjectDeclaration +import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf +import org.jetbrains.kotlin.utils.addToStdlib.safeAs //copied from old j2k fun canKeepEqEq(left: PsiExpression, right: PsiExpression?): Boolean { @@ -129,4 +134,10 @@ fun PsiClass.classKind(): JKClass.ClassKind = isEnum -> JKClass.ClassKind.ENUM isInterface -> JKClass.ClassKind.INTERFACE else -> JKClass.ClassKind.CLASS - } \ No newline at end of file + } + +val KtDeclaration.fqNameWithoutCompanions + get() = generateSequence(this) { it.containingClassOrObject } + .filter { it.safeAs()?.isCompanion() != true && it.name != null } + .toList() + .foldRight(containingKtFile.packageFqName) { container, acc -> acc.child(Name.identifier(container.name!!)) }