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
This commit is contained in:
Ilya Kirillov
2019-10-09 17:19:02 +03:00
parent 7535c2f249
commit f951ed9dba
16 changed files with 532 additions and 28 deletions
@@ -48,6 +48,7 @@ import com.intellij.psi.PsiManager
import com.intellij.psi.util.PsiTreeUtil import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.kotlin.idea.KotlinFileType import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.idea.core.util.toPsiFile 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.IdeaJavaToKotlinServices
import org.jetbrains.kotlin.idea.j2k.J2kPostProcessor import org.jetbrains.kotlin.idea.j2k.J2kPostProcessor
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand 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.j2k.*
import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.UserDataProperty import org.jetbrains.kotlin.psi.UserDataProperty
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import java.util.* import java.util.*
@@ -105,6 +107,7 @@ class JavaToKotlinAction : AnAction() {
virtualFile.pathBeforeJ2K = virtualFile.path virtualFile.pathBeforeJ2K = virtualFile.path
virtualFile.rename(this, fileName) virtualFile.rename(this, fileName)
} }
result += virtualFile
} catch (e: IOException) { } catch (e: IOException) {
MessagesEx.error(psiFile.project, e.message ?: "").showLater() MessagesEx.error(psiFile.project, e.message ?: "").showLater()
} }
@@ -165,7 +168,7 @@ class JavaToKotlinAction : AnAction() {
) return emptyList() ) return emptyList()
var externalCodeUpdate: (() -> Unit)? = null var externalCodeUpdate: ((List<KtFile>) -> Unit)? = null
if (enableExternalCodeProcessing && converterResult!!.externalCodeProcessing != null) { if (enableExternalCodeProcessing && converterResult!!.externalCodeProcessing != null) {
val question = val question =
@@ -196,16 +199,18 @@ class JavaToKotlinAction : AnAction() {
CommandProcessor.getInstance().markCurrentCommandAsGlobal(project) CommandProcessor.getInstance().markCurrentCommandAsGlobal(project)
val newFiles = saveResults(javaFiles, converterResult!!.results) val newFiles = saveResults(javaFiles, converterResult!!.results)
.map { it.toPsiFile(project) as KtFile }
.onEach { it.commitAndUnblockDocument() }
externalCodeUpdate?.invoke() externalCodeUpdate?.invoke(newFiles)
PsiDocumentManager.getInstance(project).commitAllDocuments() PsiDocumentManager.getInstance(project).commitAllDocuments()
newFiles.singleOrNull()?.let { newFiles.singleOrNull()?.let {
FileEditorManager.getInstance(project).openFile(it, true) FileEditorManager.getInstance(project).openFile(it.virtualFile, true)
} }
newFiles.map { it.toPsiFile(project) as KtFile } newFiles
} }
} }
} }
@@ -56,7 +56,7 @@ enum class ParseContext {
} }
interface ExternalCodeProcessing { interface ExternalCodeProcessing {
fun prepareWriteOperation(progress: ProgressIndicator): () -> Unit fun prepareWriteOperation(progress: ProgressIndicator?): (List<KtFile>) -> Unit
} }
@@ -190,7 +190,8 @@ class OldJavaToKotlinConverter(
if (map.isEmpty()) return null if (map.isEmpty()) return null
return object : ExternalCodeProcessing { return object : ExternalCodeProcessing {
override fun prepareWriteOperation(progress: ProgressIndicator): () -> Unit { override fun prepareWriteOperation(progress: ProgressIndicator?): (List<KtFile>) -> Unit {
if (progress == null) error("Progress should not be null for old J2K")
val refs = ArrayList<ReferenceInfo>() val refs = ArrayList<ReferenceInfo>()
progress.text = "Searching usages to update..." progress.text = "Searching usages to update..."
@@ -322,7 +323,7 @@ class OldWithProgressProcessor(private val progress: ProgressIndicator?, private
} }
} }
private class ProgressPortionReporter( class ProgressPortionReporter(
indicator: ProgressIndicator, indicator: ProgressIndicator,
private val start: Double, private val start: Double,
private val portion: Double private val portion: Double
@@ -58,11 +58,20 @@ class AccessorToPropertyProcessing(val accessorMethod: PsiMethod, val accessorKi
if (accessorMethod.hasModifierProperty(PsiModifier.PRIVATE)) if (accessorMethod.hasModifierProperty(PsiModifier.PRIVATE))
emptyList() emptyList()
else 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<PsiReference>? { override fun processUsage(reference: PsiReference): Array<PsiReference>? {
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<PsiReference>? {
val nameExpr = element as? KtSimpleNameExpression ?: return null
val callExpr = nameExpr.parent as? KtCallExpression ?: return null val callExpr = nameExpr.parent as? KtCallExpression ?: return null
val arguments = callExpr.valueArguments val arguments = callExpr.valueArguments
@@ -73,8 +82,7 @@ class AccessorToPropertyProcessing(val accessorMethod: PsiMethod, val accessorKi
if (arguments.size != 0) return null // incorrect call if (arguments.size != 0) return null // incorrect call
propertyNameExpr = callExpr.replace(propertyNameExpr) as KtSimpleNameExpression propertyNameExpr = callExpr.replace(propertyNameExpr) as KtSimpleNameExpression
return propertyNameExpr.references return propertyNameExpr.references
} } else {
else {
val value = arguments.singleOrNull()?.getArgumentExpression() ?: return null val value = arguments.singleOrNull()?.getArgumentExpression() ?: return null
var assignment = factory.createExpression("a = b") as KtBinaryExpression var assignment = factory.createExpression("a = b") as KtBinaryExpression
assignment.right!!.replace(value) assignment.right!!.replace(value)
@@ -85,8 +93,7 @@ class AccessorToPropertyProcessing(val accessorMethod: PsiMethod, val accessorKi
assignment.left!!.replace(qualifiedExpression) assignment.left!!.replace(qualifiedExpression)
assignment = qualifiedExpression.replace(assignment) as KtBinaryExpression assignment = qualifiedExpression.replace(assignment) as KtBinaryExpression
(assignment.left as KtQualifiedExpression).selectorExpression!!.references (assignment.left as KtQualifiedExpression).selectorExpression!!.references
} } else {
else {
assignment.left!!.replace(propertyNameExpr) assignment.left!!.replace(propertyNameExpr)
assignment = callExpr.replace(assignment) as KtBinaryExpression assignment = callExpr.replace(assignment) as KtBinaryExpression
assignment.left!!.references assignment.left!!.references
@@ -27,9 +27,12 @@ import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi
import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.nj2k.NewJ2kConverterContext import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.nj2k.asGetterName import org.jetbrains.kotlin.nj2k.*
import org.jetbrains.kotlin.nj2k.asSetterName 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.nj2k.postProcessing.*
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.* import org.jetbrains.kotlin.psi.psiUtil.*
@@ -284,7 +287,7 @@ class ConvertGettersAndSettersToPropertyProcessing : ElementsBasedPostProcessing
if (classesWithPropertiesData.isEmpty()) return if (classesWithPropertiesData.isEmpty()) return
runUndoTransparentActionInEdt(inWriteAction = true) { runUndoTransparentActionInEdt(inWriteAction = true) {
for ((klass, propertiesData) in classesWithPropertiesData) { for ((klass, propertiesData) in classesWithPropertiesData) {
convertClass(klass, propertiesData) convertClass(klass, propertiesData, converterContext.externalCodeProcessor)
} }
} }
} }
@@ -535,7 +538,11 @@ class ConvertGettersAndSettersToPropertyProcessing : ElementsBasedPostProcessing
setName(newName) setName(newName)
} }
private fun convertClass(klass: KtClassOrObject, propertiesData: List<PropertyData>) { private fun convertClass(
klass: KtClassOrObject,
propertiesData: List<PropertyData>,
externalCodeUpdater: NewExternalCodeProcessing
) {
val factory = KtPsiFactory(klass) val factory = KtPsiFactory(klass)
val accessors = propertiesData.filterGettersAndSetters(klass, factory) 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<RealGetter>()?.function?.fqNameWithoutCompanions
val setterFqName = setter.safeAs<RealSetter>()?.function?.fqNameWithoutCompanions
getterFqName?.let { fqName ->
externalCodeUpdater.getMember(fqName)?.safeAs<JKMethodData>()?.let {
it.usedAsAccessorOfProperty = propertyInfo ?: return@let
}
}
setterFqName?.let { fqName ->
externalCodeUpdater.getMember(fqName)?.safeAs<JKMethodData>()?.let {
it.usedAsAccessorOfProperty = propertyInfo ?: return@let
}
}
val isOpen = getter.safeAs<RealGetter>()?.function?.hasModifier(KtTokens.OPEN_KEYWORD) == true val isOpen = getter.safeAs<RealGetter>()?.function?.hasModifier(KtTokens.OPEN_KEYWORD) == true
|| setter.safeAs<RealSetter>()?.function?.hasModifier(KtTokens.OPEN_KEYWORD) == true || setter.safeAs<RealSetter>()?.function?.hasModifier(KtTokens.OPEN_KEYWORD) == true
@@ -18,11 +18,10 @@ import org.jetbrains.kotlin.nj2k.types.JKTypeFactory
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
class JKSymbolProvider(project: Project, module: Module?, contextElement: PsiElement) { class JKSymbolProvider(private val resolver: JKResolver) {
private val symbolsByFqName = mutableMapOf<String, JKSymbol>() private val symbolsByFqName = mutableMapOf<String, JKSymbol>()
val symbolsByPsi = mutableMapOf<PsiElement, JKSymbol>() val symbolsByPsi = mutableMapOf<PsiElement, JKSymbol>()
private val symbolsByJK = mutableMapOf<JKDeclaration, JKSymbol>() private val symbolsByJK = mutableMapOf<JKDeclaration, JKSymbol>()
private val resolver = JKResolver(project, module, contextElement)
private val elementVisitor = ElementVisitor() private val elementVisitor = ElementVisitor()
lateinit var typeFactory: JKTypeFactory lateinit var typeFactory: JKTypeFactory
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.nj2k
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.j2k.* import org.jetbrains.kotlin.j2k.*
import org.jetbrains.kotlin.nj2k.externalCodeProcessing.NewExternalCodeProcessing
import org.jetbrains.kotlin.nj2k.types.JKTypeFactory import org.jetbrains.kotlin.nj2k.types.JKTypeFactory
data class NewJ2kConverterContext( data class NewJ2kConverterContext(
@@ -16,7 +17,8 @@ data class NewJ2kConverterContext(
val converter: NewJavaToKotlinConverter, val converter: NewJavaToKotlinConverter,
val inConversionContext: (PsiElement) -> Boolean, val inConversionContext: (PsiElement) -> Boolean,
val importStorage: JKImportStorage, val importStorage: JKImportStorage,
val elementsInfoStorage: JKElementInfoStorage val elementsInfoStorage: JKElementInfoStorage,
val externalCodeProcessor: NewExternalCodeProcessing
) : ConverterContext { ) : ConverterContext {
val project: Project val project: Project
get() = converter.project get() = converter.project
@@ -31,12 +31,15 @@ import org.jetbrains.kotlin.idea.project.languageVersionSettings
import org.jetbrains.kotlin.idea.util.application.runWriteAction import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.j2k.* import org.jetbrains.kotlin.j2k.*
import org.jetbrains.kotlin.name.FqName 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.printing.JKCodeBuilder
import org.jetbrains.kotlin.nj2k.types.JKTypeFactory import org.jetbrains.kotlin.nj2k.types.JKTypeFactory
import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtImportList import org.jetbrains.kotlin.psi.KtImportList
import org.jetbrains.kotlin.psi.KtPsiFactory import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
import org.jetbrains.kotlin.resolve.ImportPath import org.jetbrains.kotlin.resolve.ImportPath
class NewJavaToKotlinConverter( class NewJavaToKotlinConverter(
@@ -128,7 +131,8 @@ class NewJavaToKotlinConverter(
override fun elementsToKotlin(inputElements: List<PsiElement>, processor: WithProgressProcessor): Result { override fun elementsToKotlin(inputElements: List<PsiElement>, processor: WithProgressProcessor): Result {
val phaseDescription = "Converting Java code to Kotlin code" val phaseDescription = "Converting Java code to Kotlin code"
val contextElement = inputElements.firstOrNull() ?: return Result(emptyList(), null, null) 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) val typeFactory = JKTypeFactory(symbolProvider)
symbolProvider.typeFactory = typeFactory symbolProvider.typeFactory = typeFactory
symbolProvider.preBuildTree(inputElements) symbolProvider.preBuildTree(inputElements)
@@ -152,14 +156,24 @@ class NewJavaToKotlinConverter(
processor.updateState(i, 1, phaseDescription) processor.updateState(i, 1, phaseDescription)
element to treeBuilder.buildTree(element, saveImports) 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( val context = NewJ2kConverterContext(
symbolProvider, symbolProvider,
typeFactory, typeFactory,
this, this,
{ it.containingFile in inputElements }, inConversionContext,
importStorage, importStorage,
JKElementInfoStorage() JKElementInfoStorage(),
externalCodeProcessing
) )
ConversionsRunner.doApply(asts.withIndex().mapNotNull { (i, ast) -> ConversionsRunner.doApply(asts.withIndex().mapNotNull { (i, ast) ->
processor.updateState(i, 1, phaseDescription) 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<PsiElement>): Result { override fun elementsToKotlin(inputElements: List<PsiElement>): Result {
@@ -5,7 +5,10 @@
package org.jetbrains.kotlin.nj2k.conversions 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.NewJ2kConverterContext
import org.jetbrains.kotlin.nj2k.psi
import org.jetbrains.kotlin.nj2k.tree.* import org.jetbrains.kotlin.nj2k.tree.*
@@ -13,6 +16,11 @@ class FieldToPropertyConversion(context: NewJ2kConverterContext) : RecursiveAppl
override fun applyToElement(element: JKTreeElement): JKTreeElement { override fun applyToElement(element: JKTreeElement): JKTreeElement {
if (element !is JKField) return recurse(element) if (element !is JKField) return recurse(element)
element.mutability = if (element.modality == Modality.FINAL) Mutability.IMMUTABLE else Mutability.MUTABLE element.mutability = if (element.modality == Modality.FINAL) Mutability.IMMUTABLE else Mutability.MUTABLE
element.psi<PsiField>()?.let { psi ->
context.externalCodeProcessor.addMember(JKFieldDataFromJava(psi))
}
element.modality = Modality.FINAL element.modality = Modality.FINAL
return recurse(element) return recurse(element)
} }
@@ -16,8 +16,11 @@
package org.jetbrains.kotlin.nj2k.conversions package org.jetbrains.kotlin.nj2k.conversions
import com.intellij.psi.PsiMethod
import org.jetbrains.kotlin.j2k.ast.Nullability 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.NewJ2kConverterContext
import org.jetbrains.kotlin.nj2k.psi
import org.jetbrains.kotlin.nj2k.throwAnnotation import org.jetbrains.kotlin.nj2k.throwAnnotation
import org.jetbrains.kotlin.nj2k.tree.JKMethodImpl import org.jetbrains.kotlin.nj2k.tree.JKMethodImpl
import org.jetbrains.kotlin.nj2k.tree.JKTreeElement import org.jetbrains.kotlin.nj2k.tree.JKTreeElement
@@ -34,6 +37,11 @@ class JavaMethodToKotlinFunctionConversion(context: NewJ2kConverterContext) : Re
symbolProvider symbolProvider
) )
} }
element.psi<PsiMethod>()?.let { psi ->
context.externalCodeProcessor.addMember(JKMethodData(psi))
}
return recurse(element) return recurse(element)
} }
} }
@@ -26,6 +26,9 @@ class StaticsToCompanionExtractConversion(context: NewJ2kConverterContext) : Rec
if (declaration is JKOtherModifiersOwner) { if (declaration is JKOtherModifiersOwner) {
declaration.otherModifierElements -= declaration.elementByModifier(OtherModifier.STATIC)!! declaration.otherModifierElements -= declaration.elementByModifier(OtherModifier.STATIC)!!
} }
context.externalCodeProcessor.getMember(declaration)?.let {
it.isStatic = true
}
} }
return recurse(element) return recurse(element)
} }
@@ -22,7 +22,7 @@ import org.jetbrains.kotlin.psi.psiUtil.getChildOfType
import org.jetbrains.kotlin.resolve.ImportPath 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 { private val scope = module?.let {
GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(it) GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(it)
} ?: GlobalSearchScope.allScope(project) } ?: GlobalSearchScope.allScope(project)
@@ -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<JKMemberInfoWithUsages>) {
private val conversions = mutableListOf<JKExternalConversion>()
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<PsiElement>, kotlinUsages: List<KtElement>) {
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<PsiElement>, kotlinUsages: List<KtElement>) {
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<PsiElement>,
val kotlinUsages: List<KtElement>
)
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)
}
}
@@ -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<JKExternalConversion> {
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<PsiReferenceExpression>().apply {
qualifierExpression?.replace(qualifier ?: return@apply)
}
private fun PsiElementFactory.createAssignment(target: PsiExpression, value: PsiExpression): PsiAssignmentExpression =
createExpressionFromText("x = 1", usage).cast<PsiAssignmentExpression>().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))
}
}
@@ -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<K : KtDeclaration> {
var kotlinElementPointer: SmartPsiElementPointer<K>?
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<J : PsiMember, K : KtDeclaration> : JKMemberData<K> {
val javaElement: J
override val fqName
get() = javaElement.getKotlinFqName()
}
interface JKFieldData : JKMemberData<KtProperty>
data class JKFakeFieldData(
override var isStatic: Boolean,
override var kotlinElementPointer: SmartPsiElementPointer<KtProperty>? = 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<KtProperty>? = null,
override var name: String = javaElement.name
) : JKMemberDataCameFromJava<PsiField, KtProperty>, 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<KtNamedFunction>? = null,
var usedAsAccessorOfProperty: JKFieldData? = null
) : JKMemberDataCameFromJava<PsiMethod, KtNamedFunction> {
override var name: String = javaElement.name
}
@@ -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<FqName, JKMemberData<*>>()
fun addMember(data: JKMemberData<*>) {
members[data.fqName ?: return] = data
}
fun getMember(element: JKDeclaration) =
element.psi<PsiMember>()?.getKotlinFqName()?.let(members::get)
fun getMember(fqName: FqName) =
members[fqName]
fun isExternalProcessingNeeded(): Boolean =
members.values.any { it.searchingNeeded }
private fun List<KtFile>.bindJavaDeclarationsToConvertedKotlinOnes() {
forEach { file ->
file.forEachDescendantOfType<KtDeclaration> { 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<KtFile>.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<KtFile>) -> Unit {
progress?.text = "Searching usages to update..."
val usages = mutableListOf<ExternalUsagesFixer.JKMemberInfoWithUsages>()
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<PsiElement>()
val kotlinUsages = mutableListOf<KtElement>()
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)
}
}
@@ -15,10 +15,15 @@ import com.intellij.psi.util.PsiUtil
import org.jetbrains.kotlin.j2k.ClassKind import org.jetbrains.kotlin.j2k.ClassKind
import org.jetbrains.kotlin.j2k.ReferenceSearcher import org.jetbrains.kotlin.j2k.ReferenceSearcher
import org.jetbrains.kotlin.j2k.isNullLiteral import org.jetbrains.kotlin.j2k.isNullLiteral
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.nj2k.tree.* 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.psi.psiUtil.parentsWithSelf
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
//copied from old j2k //copied from old j2k
fun canKeepEqEq(left: PsiExpression, right: PsiExpression?): Boolean { fun canKeepEqEq(left: PsiExpression, right: PsiExpression?): Boolean {
@@ -130,3 +135,9 @@ fun PsiClass.classKind(): JKClass.ClassKind =
isInterface -> JKClass.ClassKind.INTERFACE isInterface -> JKClass.ClassKind.INTERFACE
else -> JKClass.ClassKind.CLASS else -> JKClass.ClassKind.CLASS
} }
val KtDeclaration.fqNameWithoutCompanions
get() = generateSequence(this) { it.containingClassOrObject }
.filter { it.safeAs<KtObjectDeclaration>()?.isCompanion() != true && it.name != null }
.toList()
.foldRight(containingKtFile.packageFqName) { container, acc -> acc.child(Name.identifier(container.name!!)) }