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:
@@ -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
|
||||
|
||||
+40
-5
@@ -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
|
||||
}
|
||||
+118
@@ -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.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!!)) }
|
||||
|
||||
Reference in New Issue
Block a user