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 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<KtFile>) -> 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
}
}
}
@@ -56,7 +56,7 @@ enum class ParseContext {
}
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
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>()
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
@@ -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<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 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
@@ -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<PropertyData>) {
private fun convertClass(
klass: KtClassOrObject,
propertiesData: List<PropertyData>,
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<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
|| 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.*
class JKSymbolProvider(project: Project, module: Module?, contextElement: PsiElement) {
class JKSymbolProvider(private val resolver: JKResolver) {
private val symbolsByFqName = mutableMapOf<String, JKSymbol>()
val symbolsByPsi = mutableMapOf<PsiElement, JKSymbol>()
private val symbolsByJK = mutableMapOf<JKDeclaration, JKSymbol>()
private val resolver = JKResolver(project, module, contextElement)
private val elementVisitor = ElementVisitor()
lateinit var typeFactory: JKTypeFactory
@@ -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
@@ -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<PsiElement>, 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<PsiElement>): Result {
@@ -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<PsiField>()?.let { psi ->
context.externalCodeProcessor.addMember(JKFieldDataFromJava(psi))
}
element.modality = Modality.FINAL
return recurse(element)
}
@@ -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<PsiMethod>()?.let { psi ->
context.externalCodeProcessor.addMember(JKMethodData(psi))
}
return recurse(element)
}
}
@@ -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)
}
@@ -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)
@@ -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)
}
}
+12 -1
View File
@@ -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
}
}
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!!)) }