KT-7135 Java to Kotlin converter should update usages in Java and Koltin code in the rest of the project

#KT-7135 Fixed
This commit is contained in:
Valentin Kipyatkov
2015-04-07 13:06:31 +03:00
parent b810ebbb0e
commit 1afbb961ee
50 changed files with 962 additions and 156 deletions
@@ -714,7 +714,7 @@ fun main(args: Array<String>) {
} }
testGroup("j2k/tests", "j2k/testData") { testGroup("j2k/tests", "j2k/testData") {
testClass(javaClass<AbstractJavaToKotlinConverterMultiFileTest>()) { testClass(javaClass<AbstractJavaToKotlinConverterMultiFileTest>()) {
model("multiFile", extension = null) model("multiFile", extension = null, recursive = false)
} }
} }
testGroup("j2k/tests", "j2k/testData") { testGroup("j2k/tests", "j2k/testData") {
@@ -126,7 +126,8 @@ public class JetSimpleNameReference(
) )
} }
return nameElement.replace(element) nameElement.replace(element)
return expression
} }
public enum class ShorteningMode { public enum class ShorteningMode {
@@ -16,7 +16,6 @@
package org.jetbrains.kotlin.idea.actions package org.jetbrains.kotlin.idea.actions
import com.intellij.codeInsight.actions.ReformatCodeProcessor
import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.CommonDataKeys import com.intellij.openapi.actionSystem.CommonDataKeys
@@ -25,6 +24,7 @@ import com.intellij.openapi.command.CommandProcessor
import com.intellij.openapi.fileEditor.FileEditorManager import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.progress.ProgressManager import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.Messages
import com.intellij.openapi.ui.ex.MessagesEx import com.intellij.openapi.ui.ex.MessagesEx
import com.intellij.openapi.vfs.CharsetToolkit import com.intellij.openapi.vfs.CharsetToolkit
import com.intellij.openapi.vfs.VfsUtilCore import com.intellij.openapi.vfs.VfsUtilCore
@@ -32,14 +32,14 @@ import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.VirtualFileVisitor import com.intellij.openapi.vfs.VirtualFileVisitor
import com.intellij.psi.PsiJavaFile import com.intellij.psi.PsiJavaFile
import com.intellij.psi.PsiManager import com.intellij.psi.PsiManager
import com.intellij.psi.codeStyle.CodeStyleManager
import org.jetbrains.kotlin.idea.j2k.IdeaResolverForConverter import org.jetbrains.kotlin.idea.j2k.IdeaResolverForConverter
import org.jetbrains.kotlin.idea.j2k.J2kPostProcessor import org.jetbrains.kotlin.idea.j2k.J2kPostProcessor
import org.jetbrains.kotlin.idea.util.application.executeCommand
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.j2k.ConverterSettings import org.jetbrains.kotlin.j2k.ConverterSettings
import org.jetbrains.kotlin.j2k.IdeaReferenceSearcher import org.jetbrains.kotlin.j2k.IdeaReferenceSearcher
import org.jetbrains.kotlin.j2k.JavaToKotlinConverter import org.jetbrains.kotlin.j2k.JavaToKotlinConverter
import org.jetbrains.kotlin.psi.JetFile
import org.jetbrains.kotlin.utils.addIfNotNull import org.jetbrains.kotlin.utils.addIfNotNull
import java.io.IOException import java.io.IOException
import java.util.ArrayList import java.util.ArrayList
@@ -49,36 +49,53 @@ public class JavaToKotlinAction : AnAction() {
val javaFiles = selectedJavaFiles(e).toList() val javaFiles = selectedJavaFiles(e).toList()
val project = CommonDataKeys.PROJECT.getData(e.getDataContext())!! val project = CommonDataKeys.PROJECT.getData(e.getDataContext())!!
var convertedTexts: List<String>? = null var converterResult: JavaToKotlinConverter.Result? = null
fun convert() { fun convert() {
val converter = JavaToKotlinConverter(project, ConverterSettings.defaultSettings, val converter = JavaToKotlinConverter(project, ConverterSettings.defaultSettings,
IdeaReferenceSearcher, IdeaResolverForConverter, J2kPostProcessor(formatCode = true)) IdeaReferenceSearcher, IdeaResolverForConverter, J2kPostProcessor(formatCode = true))
val inputElements = javaFiles.map { JavaToKotlinConverter.InputElement(it, it) } val inputElements = javaFiles.map { JavaToKotlinConverter.InputElement(it, it) }
convertedTexts = converter.elementsToKotlin(inputElements, ProgressManager.getInstance().getProgressIndicator()) converterResult = converter.elementsToKotlin(inputElements, ProgressManager.getInstance().getProgressIndicator())
.map { it!!.text /*conversion of a file always succeeds*/ }
} }
val title = "Convert Java to Kotlin"
if (!ProgressManager.getInstance().runProcessWithProgressSynchronously( if (!ProgressManager.getInstance().runProcessWithProgressSynchronously(
{ {
runReadAction(::convert) runReadAction(::convert)
}, },
"Converting Java to Kotlin", title,
true, true,
project)) return project)) return
CommandProcessor.getInstance().executeCommand(project, object : Runnable { var externalCodeUpdate: (() -> Unit)? = null
override fun run() {
CommandProcessor.getInstance().markCurrentCommandAsGlobal(project)
val newFiles = saveResults(javaFiles, convertedTexts!!) if (converterResult!!.externalCodeProcessing != null) {
deleteFiles(javaFiles) val question = "Some code in the rest of your project may require corrections after performing this conversion. Do you want to find such code and correct it too?"
if (Messages.showOkCancelDialog(project, question, title, Messages.getQuestionIcon()) == Messages.OK) {
newFiles.singleOrNull()?.let { ProgressManager.getInstance().runProcessWithProgressSynchronously(
FileEditorManager.getInstance(project).openFile(it, true) {
} runReadAction {
externalCodeUpdate = converterResult!!.externalCodeProcessing(ProgressManager.getInstance().getProgressIndicator())
}
},
title,
true,
project)
} }
}, "Convert files from Java to Kotlin", null) }
project.executeWriteCommand("Convert files from Java to Kotlin") {
CommandProcessor.getInstance().markCurrentCommandAsGlobal(project)
val newFiles = saveResults(javaFiles, converterResult!!.results.map { it!!.text /*conversion of a file always succeeds*/ })
deleteFiles(javaFiles)
externalCodeUpdate?.invoke()
newFiles.singleOrNull()?.let {
FileEditorManager.getInstance(project).openFile(it, true)
}
}
} }
override fun update(e: AnActionEvent) { override fun update(e: AnActionEvent) {
@@ -116,9 +133,7 @@ public class JavaToKotlinAction : AnAction() {
private fun saveResults(javaFiles: List<PsiJavaFile>, convertedTexts: List<String>): List<VirtualFile> { private fun saveResults(javaFiles: List<PsiJavaFile>, convertedTexts: List<String>): List<VirtualFile> {
val result = ArrayList<VirtualFile>() val result = ArrayList<VirtualFile>()
for ((psiFile, text) in javaFiles.zip(convertedTexts)) { for ((psiFile, text) in javaFiles.zip(convertedTexts)) {
ApplicationManager.getApplication().runWriteAction { result.addIfNotNull(saveConversionResult(psiFile.getVirtualFile(), text, psiFile.getProject()))
result.addIfNotNull(saveConversionResult(psiFile.getVirtualFile(), text, psiFile.getProject()))
}
} }
return result return result
} }
@@ -137,13 +152,11 @@ public class JavaToKotlinAction : AnAction() {
private fun deleteFiles(javaFiles: List<PsiJavaFile>) { private fun deleteFiles(javaFiles: List<PsiJavaFile>) {
for (psiFile in javaFiles) { for (psiFile in javaFiles) {
ApplicationManager.getApplication().runWriteAction { try {
try { psiFile.getVirtualFile()?.delete(this)
psiFile.getVirtualFile()?.delete(this) }
} catch (e: IOException) {
catch (e: IOException) { MessagesEx.error(psiFile.getProject(), e.getMessage()).showLater()
MessagesEx.error(psiFile.getProject(), e.getMessage()).showLater()
}
} }
} }
} }
@@ -161,7 +161,7 @@ public class ConvertJavaCopyPastePostProcessor : CopyPastePostProcessor<TextBloc
) )
val inputElements = list.filterIsInstance<PsiElement>().map { JavaToKotlinConverter.InputElement(it, null) } val inputElements = list.filterIsInstance<PsiElement>().map { JavaToKotlinConverter.InputElement(it, null) }
val results = converter.elementsToKotlin(inputElements) val results = converter.elementsToKotlin(inputElements).results
var resultIndex = 0 var resultIndex = 0
val convertedCodeBuilder = StringBuilder() val convertedCodeBuilder = StringBuilder()
@@ -21,6 +21,10 @@ import com.intellij.psi.*
import org.jetbrains.kotlin.j2k.ast.* import org.jetbrains.kotlin.j2k.ast.*
import org.jetbrains.kotlin.j2k.ast.Class import org.jetbrains.kotlin.j2k.ast.Class
import org.jetbrains.kotlin.j2k.usageProcessing.AccessorToPropertyProcessing import org.jetbrains.kotlin.j2k.usageProcessing.AccessorToPropertyProcessing
import org.jetbrains.kotlin.j2k.usageProcessing.MethodIntoObjectProcessing
import org.jetbrains.kotlin.j2k.usageProcessing.ToObjectWithOnlyMethodsProcessing
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.name.SpecialNames
import java.util.ArrayList import java.util.ArrayList
import java.util.HashMap import java.util.HashMap
import java.util.HashSet import java.util.HashSet
@@ -71,6 +75,19 @@ class ClassBodyConverter(private val psiClass: PsiClass,
val rBrace = RBrace().assignPrototype(psiClass.getRBrace()) val rBrace = RBrace().assignPrototype(psiClass.getRBrace())
if (isObject) { if (isObject) {
val psiMembers = convertedMembers.keySet()
if (psiMembers.all { it is PsiMethod }) { // for object with no fields we can use faster external usage processing
converter.addUsageProcessing(ToObjectWithOnlyMethodsProcessing(psiClass))
}
else {
for (psiMember in psiMembers) {
if (psiMember is PsiMethod /* fields in object can be accessed as fields from java */
&& !psiMember.hasModifierProperty(PsiModifier.PRIVATE)) {
converter.addUsageProcessing(MethodIntoObjectProcessing(psiMember, JvmAbi.INSTANCE_FIELD))
}
}
}
return ClassBody(null, null, convertedMembers.values().toList(), emptyList(), lBrace, rBrace) return ClassBody(null, null, convertedMembers.values().toList(), emptyList(), lBrace, rBrace)
} }
@@ -87,6 +104,10 @@ class ClassBodyConverter(private val psiClass: PsiClass,
} }
else if (useCompanionObject && member !is Class && psiMember !is PsiEnumConstant && psiMember.hasModifierProperty(PsiModifier.STATIC)) { else if (useCompanionObject && member !is Class && psiMember !is PsiEnumConstant && psiMember.hasModifierProperty(PsiModifier.STATIC)) {
companionObjectMembers.add(member) companionObjectMembers.add(member)
if (psiMember is PsiMethod /* fields in companion object can be accessed as fields from java */
&& !psiMember.hasModifierProperty(PsiModifier.PRIVATE)) {
converter.addUsageProcessing(MethodIntoObjectProcessing(psiMember, SpecialNames.DEFAULT_NAME_FOR_COMPANION_OBJECT.getIdentifier()))
}
} }
else { else {
members.add(member) members.add(member)
@@ -179,10 +200,11 @@ class ClassBodyConverter(private val psiClass: PsiClass,
private fun getAccessorInfo(method: PsiMethod): AccessorInfo? { private fun getAccessorInfo(method: PsiMethod): AccessorInfo? {
val name = method.getName() val name = method.getName()
val static = method.hasModifierProperty(PsiModifier.STATIC)
if (name.startsWith("get") && method.getParameterList().getParametersCount() == 0) { if (name.startsWith("get") && method.getParameterList().getParametersCount() == 0) {
val body = method.getBody() ?: return null val body = method.getBody() ?: return null
val returnStatement = (body.getStatements().singleOrNull() as? PsiReturnStatement) ?: return null val returnStatement = (body.getStatements().singleOrNull() as? PsiReturnStatement) ?: return null
val field = fieldByExpression(returnStatement.getReturnValue()) ?: return null val field = fieldByExpression(returnStatement.getReturnValue(), static) ?: return null
if (field.getType() != method.getReturnType()) return null if (field.getType() != method.getReturnType()) return null
if (converter.typeConverter.variableMutability(field) != converter.typeConverter.methodMutability(method)) return null if (converter.typeConverter.variableMutability(field) != converter.typeConverter.methodMutability(method)) return null
val propertyName = StringUtil.decapitalize(name.substring("get".length())) val propertyName = StringUtil.decapitalize(name.substring("get".length()))
@@ -193,7 +215,7 @@ class ClassBodyConverter(private val psiClass: PsiClass,
val statement = (body.getStatements().singleOrNull() as? PsiExpressionStatement) ?: return null val statement = (body.getStatements().singleOrNull() as? PsiExpressionStatement) ?: return null
val assignment = statement.getExpression() as? PsiAssignmentExpression ?: return null val assignment = statement.getExpression() as? PsiAssignmentExpression ?: return null
if (assignment.getOperationTokenType() != JavaTokenType.EQ) return null if (assignment.getOperationTokenType() != JavaTokenType.EQ) return null
val field = fieldByExpression(assignment.getLExpression()) ?: return null val field = fieldByExpression(assignment.getLExpression(), static) ?: return null
val parameter = method.getParameterList().getParameters().single() val parameter = method.getParameterList().getParameters().single()
if ((assignment.getRExpression() as? PsiReferenceExpression)?.resolve() != parameter) return null if ((assignment.getRExpression() as? PsiReferenceExpression)?.resolve() != parameter) return null
if (field.getType() != parameter.getType()) return null if (field.getType() != parameter.getType()) return null
@@ -205,11 +227,16 @@ class ClassBodyConverter(private val psiClass: PsiClass,
} }
} }
private fun fieldByExpression(expression: PsiExpression?): PsiField? { private fun fieldByExpression(expression: PsiExpression?, static: Boolean): PsiField? {
val refExpr = expression as? PsiReferenceExpression ?: return null val refExpr = expression as? PsiReferenceExpression ?: return null
if (!refExpr.isQualifierEmptyOrThis()) return null if (static) {
if (!refExpr.isQualifierEmptyOrClass(psiClass)) return null
}
else {
if (!refExpr.isQualifierEmptyOrThis()) return null
}
val field = refExpr.resolve() as? PsiField ?: return null val field = refExpr.resolve() as? PsiField ?: return null
if (field.getContainingClass() != psiClass || field.hasModifierProperty(PsiModifier.STATIC)) return null if (field.getContainingClass() != psiClass || field.hasModifierProperty(PsiModifier.STATIC) != static) return null
return field return field
} }
} }
@@ -16,6 +16,7 @@
package org.jetbrains.kotlin.j2k package org.jetbrains.kotlin.j2k
import com.google.common.collect.Multimap
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.psi.* import com.intellij.psi.*
import com.intellij.psi.CommonClassNames.* import com.intellij.psi.CommonClassNames.*
@@ -73,7 +74,7 @@ class Converter private(
private fun createDefaultCodeConverter() = CodeConverter(this, DefaultExpressionConverter(), DefaultStatementConverter(), null) private fun createDefaultCodeConverter() = CodeConverter(this, DefaultExpressionConverter(), DefaultStatementConverter(), null)
public data class IntermediateResult( public data class IntermediateResult(
val codeGenerator: (Map<PsiElement, UsageProcessing>) -> String, val codeGenerator: (Map<PsiElement, Collection<UsageProcessing>>) -> String,
val parseContext: ParseContext val parseContext: ParseContext
) )
@@ -108,7 +109,7 @@ class Converter private(
else -> null else -> null
} }
private fun unfoldDeferredElements(usageProcessings: Map<PsiElement, UsageProcessing>) { private fun unfoldDeferredElements(usageProcessings: Map<PsiElement, Collection<UsageProcessing>>) {
val codeConverter = createDefaultCodeConverter().withSpecialExpressionConverter(UsageProcessingExpressionConverter(usageProcessings)) val codeConverter = createDefaultCodeConverter().withSpecialExpressionConverter(UsageProcessingExpressionConverter(usageProcessings))
// we use loop with index because new deferred elements can be added during unfolding // we use loop with index because new deferred elements can be added during unfolding
@@ -16,6 +16,8 @@
package org.jetbrains.kotlin.j2k package org.jetbrains.kotlin.j2k
import com.intellij.ide.util.DelegatingProgressIndicator
import com.intellij.lang.java.JavaLanguage
import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.editor.RangeMarker import com.intellij.openapi.editor.RangeMarker
import com.intellij.openapi.progress.EmptyProgressIndicator import com.intellij.openapi.progress.EmptyProgressIndicator
@@ -26,15 +28,19 @@ import com.intellij.openapi.project.Project
import com.intellij.openapi.util.TextRange import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile import com.intellij.psi.PsiFile
import com.intellij.psi.PsiNamedElement
import com.intellij.psi.PsiReference
import com.intellij.psi.impl.source.DummyHolder
import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.idea.JetLanguage
import org.jetbrains.kotlin.j2k.ast.Element import org.jetbrains.kotlin.j2k.ast.Element
import org.jetbrains.kotlin.j2k.usageProcessing.UsageProcessing import org.jetbrains.kotlin.j2k.usageProcessing.UsageProcessing
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.isAncestor import org.jetbrains.kotlin.psi.psiUtil.isAncestor
import org.jetbrains.kotlin.psi.psiUtil.parents
import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.BindingContext
import java.util.ArrayList import java.util.*
import java.util.HashMap
public trait PostProcessor { public trait PostProcessor {
public fun analyzeFile(file: JetFile, range: TextRange?): BindingContext public fun analyzeFile(file: JetFile, range: TextRange?): BindingContext
@@ -80,22 +86,26 @@ public class JavaToKotlinConverter(private val project: Project,
val postProcessingContext: PsiElement? val postProcessingContext: PsiElement?
) )
public data class Result(val text: String, val parseContext: ParseContext) public data class ElementResult(val text: String, val parseContext: ParseContext)
public data class Result(val results: List<ElementResult?>, val externalCodeProcessing: ((ProgressIndicator) -> (() -> Unit)?)?) //TODO: change interface to not perform write-actions under progress
public fun elementsToKotlin( public fun elementsToKotlin(
inputElements: List<InputElement>, inputElements: List<InputElement>,
progress: ProgressIndicator = EmptyProgressIndicator() progress: ProgressIndicator = EmptyProgressIndicator()
): List<Result?> { ): Result {
try { try {
val elementCount = inputElements.size() val elementCount = inputElements.size()
val intermediateResults = ArrayList<(Converter.IntermediateResult)?>(elementCount) val intermediateResults = ArrayList<(Converter.IntermediateResult)?>(elementCount)
val usageProcessings = HashMap<PsiElement, UsageProcessing>()
val usageProcessingCollector: (UsageProcessing) -> Unit = { usageProcessing -> val usageProcessings = LinkedHashMap<PsiElement, MutableCollection<UsageProcessing>>()
assert(!usageProcessings.containsKey(usageProcessing.targetElement)) val usageProcessingCollector: (UsageProcessing) -> Unit = {
{ "Duplicated UsageProcessing for target element ${usageProcessing.targetElement}" } usageProcessings.getOrPut(it.targetElement, { ArrayList() }).add(it)
usageProcessings.put(usageProcessing.targetElement, usageProcessing)
} }
fun inConversionScope(element: PsiElement)
= inputElements.any { it.element.isAncestor(element, strict = false) }
val progressText = "Converting Java to Kotlin" val progressText = "Converting Java to Kotlin"
val fileCountText = elementCount.toString() + " " + if (elementCount > 1) "files" else "file" val fileCountText = elementCount.toString() + " " + if (elementCount > 1) "files" else "file"
var fraction = 0.0 var fraction = 0.0
@@ -128,31 +138,29 @@ public class JavaToKotlinConverter(private val project: Project,
processFilesWithProgress(0.25) { i -> processFilesWithProgress(0.25) { i ->
val psiElement = inputElements[i].element val psiElement = inputElements[i].element
fun inConversionScope(element: PsiElement)
= inputElements.any { it.element.isAncestor(element, strict = false) }
val converter = Converter.create(psiElement, settings, ::inConversionScope, referenceSearcher, resolverForConverter, usageProcessingCollector) val converter = Converter.create(psiElement, settings, ::inConversionScope, referenceSearcher, resolverForConverter, usageProcessingCollector)
val result = converter.convert() val result = converter.convert()
intermediateResults.add(result) intermediateResults.add(result)
} }
val results = ArrayList<Result?>(elementCount) val results = ArrayList<ElementResult?>(elementCount)
processFilesWithProgress(0.25) { i -> processFilesWithProgress(0.25) { i ->
val result = intermediateResults[i] val result = intermediateResults[i]
results.add(if (result != null) results.add(if (result != null)
Result(result.codeGenerator(usageProcessings), result.parseContext) ElementResult(result.codeGenerator(usageProcessings), result.parseContext)
else else
null) null)
intermediateResults[i] = null // to not hold unused objects in the heap intermediateResults[i] = null // to not hold unused objects in the heap
} }
val externalCodeProcessing = buildExternalCodeProcessing(usageProcessings, ::inConversionScope)
if (postProcessor == null) { if (postProcessor == null) {
assert(progress is EmptyProgressIndicator, "Progress indicator not supported for postProcessor == null") assert(progress is EmptyProgressIndicator, "Progress indicator not supported for postProcessor == null")
return results return Result(results, externalCodeProcessing)
} }
val finalResults = ArrayList<Result?>(elementCount) val finalResults = ArrayList<ElementResult?>(elementCount)
processFilesWithProgress(0.5) { i -> processFilesWithProgress(0.5) { i ->
val result = results[i] val result = results[i]
if (result != null) { if (result != null) {
@@ -160,7 +168,7 @@ public class JavaToKotlinConverter(private val project: Project,
//TODO: post processing does not work correctly for ParseContext different from TOP_LEVEL //TODO: post processing does not work correctly for ParseContext different from TOP_LEVEL
val kotlinFile = JetPsiFactory(project).createAnalyzableFile("dummy.kt", result.text, inputElements[i].postProcessingContext!!) val kotlinFile = JetPsiFactory(project).createAnalyzableFile("dummy.kt", result.text, inputElements[i].postProcessingContext!!)
AfterConversionPass(project, postProcessor).run(kotlinFile, range = null) AfterConversionPass(project, postProcessor).run(kotlinFile, range = null)
finalResults.add(Result(kotlinFile.getText(), result.parseContext)) finalResults.add(ElementResult(kotlinFile.getText(), result.parseContext))
} }
catch(e: ProcessCanceledException) { catch(e: ProcessCanceledException) {
throw e throw e
@@ -175,7 +183,7 @@ public class JavaToKotlinConverter(private val project: Project,
} }
} }
return finalResults return Result(finalResults, externalCodeProcessing)
} }
catch(e: ElementCreationStackTraceRequiredException) { catch(e: ElementCreationStackTraceRequiredException) {
// if we got this exception then we need to turn element creation stack traces on to get better diagnostic // if we got this exception then we need to turn element creation stack traces on to get better diagnostic
@@ -188,4 +196,130 @@ public class JavaToKotlinConverter(private val project: Project,
} }
} }
} }
data class ReferenceInfo(
val reference: PsiReference,
val target: PsiElement,
val file: PsiFile,
val processings: Collection<UsageProcessing>
)
private fun buildExternalCodeProcessing(
usageProcessings: Map<PsiElement, Collection<UsageProcessing>>,
inConversionScope: (PsiElement) -> Boolean
): ((ProgressIndicator) -> (() -> Unit)?)? {
if (usageProcessings.isEmpty()) return null
val map: Map<PsiElement, Collection<UsageProcessing>> = usageProcessings.values()
.flatMap { it }
.filter { it.javaCodeProcessor != null || it.kotlinCodeProcessor != null }
.groupBy { it.targetElement }
if (map.isEmpty()) return null
return fun(progress: ProgressIndicator): (() -> Unit)? {
val refs = ArrayList<ReferenceInfo>()
progress.setText("Searching usages to update...")
for ((i, entry) in map.entrySet().withIndex()) {
val psiElement = entry.key
val processings = entry.value
progress.setText2((psiElement as? PsiNamedElement)?.getName() ?: "")
progress.checkCanceled()
ProgressManager.getInstance().runProcess(
{
val searchJava = processings.any { it.javaCodeProcessor != null }
val searchKotlin = processings.any { it.kotlinCodeProcessor != null }
referenceSearcher.findExternalCodeProcessingUsages(psiElement, searchJava, searchKotlin)
.filterNot { inConversionScope(it.getElement()) }
.mapTo(refs) { ReferenceInfo(it, psiElement, it.getElement().getContainingFile(), processings) }
},
ProgressPortionReporter(progress, i / map.size().toDouble(), 1.0 / map.size()))
}
if (refs.isEmpty()) return null
return { processUsages(refs) }
}
}
private fun processUsages(refs: Collection<ReferenceInfo>) {
@ReferenceLoop
for ((reference, target, file, processings) in refs.sortBy(ReferenceComparator)) {
val processors = when (reference.getElement().getLanguage()) {
JavaLanguage.INSTANCE -> processings.map { it.javaCodeProcessor }.filterNotNull()
JetLanguage.INSTANCE -> processings.map { it.kotlinCodeProcessor }.filterNotNull()
else -> continue@ReferenceLoop
}
checkReferenceValid(reference)
var references = listOf(reference)
for (processor in processors) {
references = references.flatMap { processor.processUsage(it) ?: listOf(it) }
references.forEach { checkReferenceValid(it) }
}
}
}
private fun checkReferenceValid(reference: PsiReference) {
val element = reference.getElement()
assert(element.isValid() && element.getContainingFile() !is DummyHolder) { "Reference $reference got invalidated" }
}
private object ReferenceComparator : Comparator<ReferenceInfo> {
override fun compare(info1: ReferenceInfo, info2: ReferenceInfo): Int {
val element1 = info1.reference.getElement()
val element2 = info2.reference.getElement()
val deepness1 = element1.deepnessInTree()
val deepness2 = element2.deepnessInTree()
if (deepness1 != deepness2) { // put deeper elements first to not invalidate them when processing ancestors
return -deepness1.compareTo(deepness2)
}
// process elements of the same deepness from right to left so that right-side of assignments is not invalidated by processing of the left one
return -element1.getStartOffsetInParent().compareTo(element2.getStartOffsetInParent())
}
private fun PsiElement.deepnessInTree() = parents(withItself = true).takeWhile { it !is PsiFile }.count()
}
private class ProgressPortionReporter(
indicator: ProgressIndicator,
private val start: Double,
private val portion: Double
) : DelegatingProgressIndicator(indicator) {
init {
setFraction(0.0)
}
override fun start() {
setFraction(0.0)
}
override fun stop() {
setFraction(portion)
}
override fun setFraction(fraction: Double) {
super.setFraction(start + (fraction * portion))
}
override fun getFraction(): Double {
return (super.getFraction() - start) / portion
}
override fun setText(text: String?) {
}
override fun setText2(text: String?) {
}
}
} }
@@ -135,7 +135,7 @@ public object JavaToKotlinTranslator {
val file = createFile(javaCode) val file = createFile(javaCode)
if (file is PsiJavaFile) { if (file is PsiJavaFile) {
val converter = JavaToKotlinConverter(file.getProject(), ConverterSettings.defaultSettings, EmptyReferenceSearcher, EmptyResolverForConverter, null) val converter = JavaToKotlinConverter(file.getProject(), ConverterSettings.defaultSettings, EmptyReferenceSearcher, EmptyResolverForConverter, null)
return prettify(converter.elementsToKotlin(listOf(JavaToKotlinConverter.InputElement(file, null))).single()!!.text) return prettify(converter.elementsToKotlin(listOf(JavaToKotlinConverter.InputElement(file, null))).results.single()!!.text)
} }
return "" return ""
} }
@@ -16,49 +16,67 @@
package org.jetbrains.kotlin.j2k package org.jetbrains.kotlin.j2k
import com.intellij.psi.PsiVariable import com.intellij.lang.java.JavaLanguage
import com.intellij.psi.PsiElement import com.intellij.openapi.fileTypes.FileTypeManager
import com.intellij.psi.PsiReferenceExpression import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.search.searches.ReferencesSearch import com.intellij.psi.*
import com.intellij.psi.search.DelegatingGlobalSearchScope
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.search.LocalSearchScope import com.intellij.psi.search.LocalSearchScope
import com.intellij.psi.PsiMethod
import com.intellij.psi.PsiMethodCallExpression
import com.intellij.psi.PsiClass
import com.intellij.psi.search.searches.ClassInheritorsSearch import com.intellij.psi.search.searches.ClassInheritorsSearch
import com.intellij.psi.search.searches.OverridingMethodsSearch import com.intellij.psi.search.searches.OverridingMethodsSearch
import com.intellij.psi.search.searches.ReferencesSearch
import org.jetbrains.kotlin.idea.JetLanguage
public trait ReferenceSearcher { public trait ReferenceSearcher {
fun findVariableUsages(variable: PsiVariable, scope: PsiElement): Collection<PsiReferenceExpression> fun findLocalUsages(element: PsiElement, scope: PsiElement): Collection<PsiReference>
fun findMethodCalls(method: PsiMethod, scope: PsiElement): Collection<PsiMethodCallExpression>
fun hasInheritors(`class`: PsiClass): Boolean fun hasInheritors(`class`: PsiClass): Boolean
fun hasOverrides(method: PsiMethod): Boolean fun hasOverrides(method: PsiMethod): Boolean
fun findExternalCodeProcessingUsages(element: PsiElement, searchJava: Boolean, searchKotlin: Boolean): Collection<PsiReference>
}
public fun ReferenceSearcher.findVariableUsages(variable: PsiVariable, scope: PsiElement): Collection<PsiReferenceExpression>
= findLocalUsages(variable, scope).filterIsInstance<PsiReferenceExpression>()
public fun ReferenceSearcher.findMethodCalls(method: PsiMethod, scope: PsiElement): Collection<PsiMethodCallExpression> {
return findLocalUsages(method, scope).map {
if (it is PsiReferenceExpression) {
val methodCall = it.getParent() as? PsiMethodCallExpression
if (methodCall?.getMethodExpression() == it) methodCall else null
}
else {
null
}
}.filterNotNull()
} }
public object EmptyReferenceSearcher: ReferenceSearcher { public object EmptyReferenceSearcher: ReferenceSearcher {
override fun findVariableUsages(variable: PsiVariable, scope: PsiElement) = listOf<PsiReferenceExpression>() override fun findLocalUsages(element: PsiElement, scope: PsiElement): Collection<PsiReference> = emptyList()
override fun findMethodCalls(method: PsiMethod, scope: PsiElement) = listOf<PsiMethodCallExpression>()
override fun hasInheritors(`class`: PsiClass) = false override fun hasInheritors(`class`: PsiClass) = false
override fun hasOverrides(method: PsiMethod) = false override fun hasOverrides(method: PsiMethod) = false
override fun findExternalCodeProcessingUsages(element: PsiElement, searchJava: Boolean, searchKotlin: Boolean): Collection<PsiReference>
= throw UnsupportedOperationException()
} }
public object IdeaReferenceSearcher : ReferenceSearcher { public object IdeaReferenceSearcher: ReferenceSearcher {
override fun findVariableUsages(variable: PsiVariable, scope: PsiElement): Collection<PsiReferenceExpression> { override fun findLocalUsages(element: PsiElement, scope: PsiElement) = ReferencesSearch.search(element, LocalSearchScope(scope)).findAll()
return ReferencesSearch.search(variable, LocalSearchScope(scope)).findAll().filterIsInstance<PsiReferenceExpression>()
}
override fun findMethodCalls(method: PsiMethod, scope: PsiElement): Collection<PsiMethodCallExpression> {
return ReferencesSearch.search(method, LocalSearchScope(scope)).findAll().map {
if (it is PsiReferenceExpression) {
val methodCall = it.getParent() as? PsiMethodCallExpression
if (methodCall?.getMethodExpression() == it) methodCall else null
}
else {
null
}
}.filterNotNull()
}
override fun hasInheritors(`class`: PsiClass) = ClassInheritorsSearch.search(`class`, false).any() override fun hasInheritors(`class`: PsiClass) = ClassInheritorsSearch.search(`class`, false).any()
override fun hasOverrides(method: PsiMethod) = OverridingMethodsSearch.search(method, false).any() override fun hasOverrides(method: PsiMethod) = OverridingMethodsSearch.search(method, false).any()
override fun findExternalCodeProcessingUsages(element: PsiElement, searchJava: Boolean, searchKotlin: Boolean): Collection<PsiReference> {
val searchScope = object : DelegatingGlobalSearchScope(GlobalSearchScope.projectScope(element.getProject())) {
override fun contains(file: VirtualFile): Boolean {
if (!super.contains(file)) return false
return when (FileTypeManager.getInstance().getFileTypeByFile(file)) {
JavaLanguage.INSTANCE.getAssociatedFileType() -> searchJava
JetLanguage.INSTANCE.getAssociatedFileType() -> searchKotlin
else -> false
}
}
}
return ReferencesSearch.search(element, searchScope).findAll()
}
} }
@@ -79,6 +79,11 @@ fun PsiReferenceExpression.isQualifierEmptyOrThis(): Boolean {
return qualifier == null || (qualifier is PsiThisExpression && qualifier.getQualifier() == null) return qualifier == null || (qualifier is PsiThisExpression && qualifier.getQualifier() == null)
} }
fun PsiReferenceExpression.isQualifierEmptyOrClass(psiClass: PsiClass): Boolean {
val qualifier = getQualifierExpression()
return qualifier == null || (qualifier is PsiReferenceExpression && qualifier.isReferenceTo(psiClass))
}
fun PsiElement.isInSingleLine(): Boolean { fun PsiElement.isInSingleLine(): Boolean {
if (this is PsiWhiteSpace) { if (this is PsiWhiteSpace) {
val text = getText()!! val text = getText()!!
@@ -17,8 +17,10 @@
package org.jetbrains.kotlin.j2k.usageProcessing package org.jetbrains.kotlin.j2k.usageProcessing
import com.intellij.psi.* import com.intellij.psi.*
import org.jetbrains.kotlin.j2k.* import org.jetbrains.kotlin.j2k.AccessorKind
import org.jetbrains.kotlin.j2k.CodeConverter
import org.jetbrains.kotlin.j2k.ast.* import org.jetbrains.kotlin.j2k.ast.*
import org.jetbrains.kotlin.psi.*
class AccessorToPropertyProcessing(val accessorMethod: PsiMethod, val accessorKind: AccessorKind, val propertyName: String) : UsageProcessing { class AccessorToPropertyProcessing(val accessorMethod: PsiMethod, val accessorKind: AccessorKind, val propertyName: String) : UsageProcessing {
override val targetElement: PsiElement get() = accessorMethod override val targetElement: PsiElement get() = accessorMethod
@@ -34,11 +36,11 @@ class AccessorToPropertyProcessing(val accessorMethod: PsiMethod, val accessorKi
val propertyAccess = QualifiedExpression(codeConverter.convertExpression(methodExpr.getQualifierExpression()), propertyName).assignNoPrototype() val propertyAccess = QualifiedExpression(codeConverter.convertExpression(methodExpr.getQualifierExpression()), propertyName).assignNoPrototype()
if (accessorKind == AccessorKind.GETTER) { if (accessorKind == AccessorKind.GETTER) {
if (arguments.size != 0) return null // incorrect call if (arguments.size() != 0) return null // incorrect call
return propertyAccess return propertyAccess
} }
else { else {
if (arguments.size != 1) return null // incorrect call if (arguments.size() != 1) return null // incorrect call
val argument = codeConverter.convertExpression(arguments[0]) val argument = codeConverter.convertExpression(arguments[0])
return AssignmentExpression(propertyAccess, argument, "=") return AssignmentExpression(propertyAccess, argument, "=")
} }
@@ -47,10 +49,42 @@ class AccessorToPropertyProcessing(val accessorMethod: PsiMethod, val accessorKi
override val javaCodeProcessor: ExternalCodeProcessor? = null override val javaCodeProcessor: ExternalCodeProcessor? = null
override val kotlinCodeProcessor: ExternalCodeProcessor? = object: ExternalCodeProcessor { override val kotlinCodeProcessor: ExternalCodeProcessor? = if (accessorMethod.hasModifierProperty(PsiModifier.PRIVATE))
override fun processUsage(reference: PsiReference) { null
//TODO else
//reference.getElement() as JetSimpleNameExpression object : ExternalCodeProcessor {
override fun processUsage(reference: PsiReference): Collection<PsiReference>? {
val nameExpr = reference.getElement() as? JetSimpleNameExpression ?: return null
val callExpr = nameExpr.getParent() as? JetCallExpression ?: return null
val arguments = callExpr.getValueArguments()
val factory = JetPsiFactory(nameExpr.getProject())
var propertyNameExpr = factory.createSimpleName(propertyName)
if (accessorKind == AccessorKind.GETTER) {
if (arguments.size() != 0) return null // incorrect call
propertyNameExpr = callExpr.replace(propertyNameExpr) as JetSimpleNameExpression
return listOf(propertyNameExpr.getReference())
}
else {
val value = arguments.singleOrNull()?.getArgumentExpression() ?: return null
var assignment = factory.createExpression("a = b") as JetBinaryExpression
assignment.getRight()!!.replace(value)
val qualifiedExpression = callExpr.getParent() as? JetQualifiedExpression
if (qualifiedExpression != null && qualifiedExpression.getSelectorExpression() == callExpr) {
callExpr.replace(propertyNameExpr)
assignment.getLeft()!!.replace(qualifiedExpression)
assignment = qualifiedExpression.replace(assignment) as JetBinaryExpression
return listOf((assignment.getLeft() as JetQualifiedExpression).getSelectorExpression()!!.getReference())
}
else {
assignment.getLeft()!!.replace(propertyNameExpr)
assignment = callExpr.replace(assignment) as JetBinaryExpression
return listOf(assignment.getLeft()!!.getReference())
}
}
}
} }
}
} }
@@ -0,0 +1,26 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.j2k.usageProcessing
import com.intellij.psi.PsiReference
class ElementRenamedCodeProcessor(private val newName: String) : ExternalCodeProcessor {
override fun processUsage(reference: PsiReference): Collection<PsiReference>? {
val newReference = reference.handleElementRename(newName).getReference()!!
return listOf(newReference)
}
}
@@ -17,44 +17,135 @@
package org.jetbrains.kotlin.j2k.usageProcessing package org.jetbrains.kotlin.j2k.usageProcessing
import com.intellij.psi.* import com.intellij.psi.*
import com.intellij.util.IncorrectOperationException
import org.jetbrains.kotlin.j2k.AccessorKind
import org.jetbrains.kotlin.j2k.CodeConverter import org.jetbrains.kotlin.j2k.CodeConverter
import org.jetbrains.kotlin.j2k.ast.* import org.jetbrains.kotlin.j2k.ast.*
import com.intellij.util.IncorrectOperationException import org.jetbrains.kotlin.psi.JetPsiFactory
import org.jetbrains.kotlin.psi.JetSimpleNameExpression
class FieldToPropertyProcessing(val field: PsiField, val propertyName: String, val isNullable: Boolean) : UsageProcessing { class FieldToPropertyProcessing(val field: PsiField, val propertyName: String, val isNullable: Boolean) : UsageProcessing {
override val targetElement: PsiElement get() = field override val targetElement: PsiElement get() = field
override val convertedCodeProcessor = if (field.getName() != propertyName) { override val convertedCodeProcessor = if (field.getName() != propertyName) MyConvertedCodeProcessor() else null
object: ConvertedCodeProcessor {
override fun convertVariableUsage(expression: PsiReferenceExpression, codeConverter: CodeConverter): Expression? {
val identifier = Identifier(propertyName, isNullable).assignNoPrototype()
val qualifier = expression.getQualifierExpression() override var javaCodeProcessor = if (field.hasModifierProperty(PsiModifier.PRIVATE))
if (qualifier != null) { null
return QualifiedExpression(codeConverter.convertExpression(qualifier), identifier) else if (!field.hasModifierProperty(PsiModifier.STATIC))
UseAccessorsJavaCodeProcessor()
else if (field.getName() != propertyName)
ElementRenamedCodeProcessor(propertyName)
else
null
override val kotlinCodeProcessor = if (field.getName() != propertyName) ElementRenamedCodeProcessor(propertyName) else null
private inner class MyConvertedCodeProcessor : ConvertedCodeProcessor {
override fun convertVariableUsage(expression: PsiReferenceExpression, codeConverter: CodeConverter): Expression? {
val identifier = Identifier(propertyName, isNullable).assignNoPrototype()
val qualifier = expression.getQualifierExpression()
if (qualifier != null) {
return QualifiedExpression(codeConverter.convertExpression(qualifier), identifier)
}
else {
// check if field name is shadowed
val elementFactory = PsiElementFactory.SERVICE.getInstance(expression.getProject())
val refExpr = try {
elementFactory.createExpressionFromText(propertyName, expression) as? PsiReferenceExpression ?: return identifier
} }
else { catch(e: IncorrectOperationException) {
// check if field name is shadowed return identifier
val elementFactory = PsiElementFactory.SERVICE.getInstance(expression.getProject())
val refExpr = try {
elementFactory.createExpressionFromText(propertyName, expression) as? PsiReferenceExpression ?: return identifier
}
catch(e: IncorrectOperationException) {
return identifier
}
return if (refExpr.resolve() == null)
identifier
else
QualifiedExpression(ThisExpression(Identifier.Empty).assignNoPrototype(), identifier) //TODO: this is not correct in case of nested/anonymous classes
} }
return if (refExpr.resolve() == null)
identifier
else
QualifiedExpression(ThisExpression(Identifier.Empty).assignNoPrototype(), identifier) //TODO: this is not correct in case of nested/anonymous classes
} }
} }
} }
else {
null private inner class UseAccessorsJavaCodeProcessor : ExternalCodeProcessor {
private val factory = PsiElementFactory.SERVICE.getInstance(field.getProject())
override fun processUsage(reference: PsiReference): Collection<PsiReference>? {
val refExpr = reference.getElement() as? PsiReferenceExpression ?: return null
val qualifier = refExpr.getQualifierExpression()
val parent = refExpr.getParent()
when (parent) {
is PsiAssignmentExpression -> {
if (refExpr == parent.getLExpression()) {
if (parent.getOperationTokenType() == JavaTokenType.EQ) {
val callExpr = parent.replace(generateSetterCall(qualifier, parent.getRExpression() ?: return null)) as PsiMethodCallExpression
return listOf(callExpr.getMethodExpression())
}
else {
val assignmentOpText = parent.getOperationSign().getText()
assert(assignmentOpText.endsWith("="))
val opText = assignmentOpText.substring(0, assignmentOpText.length() - 1)
return parent.replaceWithModificationCalls(qualifier, opText, parent.getRExpression() ?: return null)
}
}
}
is PsiPrefixExpression, is PsiPostfixExpression -> {
//TODO: what if it's used as value?
val operationType = if (parent is PsiPrefixExpression)
parent.getOperationTokenType()
else
(parent as PsiPostfixExpression).getOperationTokenType()
val opText = when (operationType) {
JavaTokenType.PLUSPLUS -> "+"
JavaTokenType.MINUSMINUS -> "-"
else -> null
}
if (opText != null) {
return (parent as PsiExpression).replaceWithModificationCalls(qualifier, opText, factory.createExpressionFromText("1", null))
}
}
}
val callExpr = refExpr.replace(generateGetterCall(qualifier)) as PsiMethodCallExpression
return listOf(callExpr.getMethodExpression())
}
//TODO: what if qualifier has side effects?
private fun PsiExpression.replaceWithModificationCalls(qualifier: PsiExpression?, op: String, value: PsiExpression): Collection<PsiReference> {
var getCall = generateGetterCall(qualifier)
var binary = factory.createExpressionFromText("x $op y", null) as PsiBinaryExpression
binary.getLOperand().replace(getCall)
binary.getROperand()!!.replace(value)
var setCall = generateSetterCall(qualifier, binary) as PsiMethodCallExpression
setCall = this.replace(setCall) as PsiMethodCallExpression
binary = setCall.getArgumentList().getExpressions().single() as PsiBinaryExpression
getCall = binary.getLOperand() as PsiMethodCallExpression
return listOf(getCall.getMethodExpression().getReference(), setCall.getMethodExpression().getReference())
}
private fun generateGetterCall(qualifier: PsiExpression?): PsiMethodCallExpression {
val text = accessorName(AccessorKind.GETTER) + "()"
val expressionText = if (qualifier != null)
"${qualifier.getText()}.$text"
else
text
return factory.createExpressionFromText(expressionText, null) as PsiMethodCallExpression
}
private fun generateSetterCall(qualifier: PsiExpression?, value: PsiExpression): PsiExpression {
val text = accessorName(AccessorKind.SETTER) + "(" + value.getText() + ")"
val expressionText = if (qualifier != null)
"${qualifier.getText()}.$text"
else
text
return factory.createExpressionFromText(expressionText, null)
}
} }
override val javaCodeProcessor: ExternalCodeProcessor? get() = null //TODO private fun accessorName(kind: AccessorKind)
= (if (kind == AccessorKind.GETTER) "get" else "set") + propertyName.capitalize()
override val kotlinCodeProcessor: ExternalCodeProcessor? get() = null //TODO
} }
@@ -0,0 +1,45 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.j2k.usageProcessing
import com.intellij.psi.*
public class MethodIntoObjectProcessing(private val method: PsiMethod, private val objectName: String) : UsageProcessing {
override val targetElement: PsiElement get() = method
override val convertedCodeProcessor: ConvertedCodeProcessor? get() = null
override val javaCodeProcessor = object: ExternalCodeProcessor {
override fun processUsage(reference: PsiReference): Collection<PsiReference>? {
val refExpr = reference.getElement() as? PsiReferenceExpression ?: return null
val qualifier = refExpr.getQualifierExpression()
val factory = PsiElementFactory.SERVICE.getInstance(method.getProject())
if (qualifier != null) {
val newQualifier = factory.createExpressionFromText(qualifier.getText() + "." + objectName, null)
qualifier.replace(newQualifier)
return listOf(reference)
}
else {
var qualifiedExpr = factory.createExpressionFromText(objectName + "." + refExpr.getText(), null) as PsiReferenceExpression
qualifiedExpr = refExpr.replace(qualifiedExpr) as PsiReferenceExpression
return listOf(qualifiedExpr.getReference())
}
}
}
override val kotlinCodeProcessor: ExternalCodeProcessor? get() = null
}
@@ -0,0 +1,38 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.j2k.usageProcessing
import com.intellij.psi.*
import org.jetbrains.kotlin.load.java.JvmAbi
class ToObjectWithOnlyMethodsProcessing(private val psiClass: PsiClass) : UsageProcessing {
override val targetElement: PsiElement get() = psiClass
override val convertedCodeProcessor: ConvertedCodeProcessor? get() = null
override val javaCodeProcessor = object: ExternalCodeProcessor {
override fun processUsage(reference: PsiReference): Collection<PsiReference>? {
val refExpr = reference.getElement() as? PsiReferenceExpression ?: return null
val factory = PsiElementFactory.SERVICE.getInstance(psiClass.getProject())
var qualifiedExpr = factory.createExpressionFromText(refExpr.getText() + "." + JvmAbi.INSTANCE_FIELD, null) as PsiReferenceExpression
qualifiedExpr = refExpr.replace(qualifiedExpr) as PsiReferenceExpression
return listOf(qualifiedExpr.getReference())
}
}
override val kotlinCodeProcessor: ExternalCodeProcessor? get() = null
}
@@ -16,10 +16,11 @@
package org.jetbrains.kotlin.j2k.usageProcessing package org.jetbrains.kotlin.j2k.usageProcessing
import org.jetbrains.kotlin.j2k.ast.Expression import com.google.common.collect.Multimap
import com.intellij.psi.* import com.intellij.psi.*
import org.jetbrains.kotlin.j2k.CodeConverter import org.jetbrains.kotlin.j2k.CodeConverter
import org.jetbrains.kotlin.j2k.SpecialExpressionConverter import org.jetbrains.kotlin.j2k.SpecialExpressionConverter
import org.jetbrains.kotlin.j2k.ast.Expression
trait UsageProcessing { trait UsageProcessing {
val targetElement: PsiElement val targetElement: PsiElement
@@ -35,24 +36,32 @@ trait ConvertedCodeProcessor {
} }
trait ExternalCodeProcessor { trait ExternalCodeProcessor {
fun processUsage(reference: PsiReference) fun processUsage(reference: PsiReference): Collection<PsiReference>?
} }
class UsageProcessingExpressionConverter(val processings: Map<PsiElement, UsageProcessing>) : SpecialExpressionConverter { class UsageProcessingExpressionConverter(val processings: Map<PsiElement, Collection<UsageProcessing>>) : SpecialExpressionConverter {
override fun convertExpression(expression: PsiExpression, codeConverter: CodeConverter): Expression? { override fun convertExpression(expression: PsiExpression, codeConverter: CodeConverter): Expression? {
if (processings.isEmpty()) return null if (processings.isEmpty()) return null
when (expression) { when (expression) {
is PsiReferenceExpression -> { is PsiReferenceExpression -> {
val target = expression.resolve() as? PsiVariable ?: return null val target = expression.resolve() as? PsiVariable ?: return null
val processor = processings[target]?.convertedCodeProcessor ?: return null val forTarget = processings[target] ?: return null
return processor.convertVariableUsage(expression, codeConverter) for (processing in forTarget) {
val converted = processing.convertedCodeProcessor?.convertVariableUsage(expression, codeConverter)
if (converted != null) return converted
}
return null
} }
is PsiMethodCallExpression -> { is PsiMethodCallExpression -> {
val target = expression.getMethodExpression().resolve() as? PsiMethod ?: return null val target = expression.getMethodExpression().resolve() as? PsiMethod ?: return null
val processor = processings[target]?.convertedCodeProcessor ?: return null val forTarget = processings[target] ?: return null
return processor.convertMethodUsage(expression, codeConverter) for (processing in forTarget) {
val converted = processing.convertedCodeProcessor?.convertMethodUsage(expression, codeConverter)
if (converted != null) return converted
}
return null
} }
else -> return null else -> return null
@@ -0,0 +1,15 @@
public class AAA {
public void foo() {
setX(getX() + 1);
}
private static int ourX = 42;
public static int getX() {
return ourX;
}
public static void setX(int x) {
ourX = x;
}
}
@@ -0,0 +1,10 @@
public class AAA {
public fun foo() {
x = x + 1
}
companion object {
public var x: Int = 42
}
}
@@ -0,0 +1,33 @@
public class AAA {
private static int ourX = 42;
private static int y = 0;
private static int z = 0;
public static int getX() {
return ourX;
}
public static void setX(int x) {
ourX = x;
}
public static int getY() {
return y;
}
public static void setY(int y) {
AAA.y = y;
}
public static int getZ() {
return z;
}
public static void setZ(int z) {
Other.z = z;
}
}
class Other {
public static int z = 0;
}
@@ -0,0 +1,13 @@
public object AAA {
public var x: Int = 42
public var y: Int = 0
public val z: Int = 0
public fun setZ(z: Int) {
Other.z = z
}
}
object Other {
public var z: Int = 0
}
@@ -0,0 +1,11 @@
package test;
class JavaClass {
public int field = 0;
protected int myProperty = 0;
public int getProperty() {
return myProperty;
}
}
@@ -0,0 +1,8 @@
package test
open class JavaClass {
public var field: Int = 0
public var property: Int = 0
protected set
}
@@ -0,0 +1,12 @@
package test;
class C extends JavaClass {
public void foo(JavaClass javaClass) {
javaClass.field++;
--javaClass.field;
myProperty = javaClass.field;
javaClass.field -= field;
field = myProperty;
field *= 2;
}
}
@@ -0,0 +1,12 @@
package test;
class C extends JavaClass {
public void foo(JavaClass javaClass) {
javaClass.setField(javaClass.getField() + 1);
javaClass.setField(javaClass.getField() - 1);
setProperty(javaClass.getField());
javaClass.setField(javaClass.getField() - getField());
setField(getProperty());
setField(getField() * 2);
}
}
@@ -0,0 +1,10 @@
package test
class C : JavaClass() {
public fun foo(javaClass: JavaClass) {
javaClass.field++
myProperty = javaClass.field
field = myProperty
field *= 2
}
}
@@ -0,0 +1,10 @@
package test
class C : JavaClass() {
public fun foo(javaClass: JavaClass) {
javaClass.field++
property = javaClass.field
field = property
field *= 2
}
}
@@ -0,0 +1,11 @@
fun foo1(a: AAA) {
a.setX(a.getX() + 1)
}
fun foo2(a: AAA?) {
a?.setX((a?.getX() ?: 0) + 1)
}
fun AAA.foo() {
setX(getX() + 1)
}
@@ -0,0 +1,11 @@
fun foo1(a: AAA) {
a.x = a.x + 1
}
fun foo2(a: AAA?) {
a?.x = (a?.x ?: 0) + 1
}
fun AAA.foo() {
x = x + 1
}
@@ -0,0 +1,3 @@
void foo(AAA a) {
a.setX(a.getX() + 1)
}
@@ -0,0 +1,3 @@
void foo(AAA a) {
a.setX(a.getX() + 1)
}
@@ -0,0 +1,14 @@
package test;
class ClassWithStatics {
public void instanceMethod(){}
public static void staticMethod(int p) {}
public static final int staticField = 1;
protected static int ourValue = 0;
public static int getValue() { return ourValue; }
public static void setValue(int value) { ourValue = value; }
}
@@ -0,0 +1,16 @@
package test
open class ClassWithStatics {
public fun instanceMethod() {
}
companion object {
public fun staticMethod(p: Int) {
}
public val staticField: Int = 1
public var value: Int = 0
}
}
@@ -0,0 +1,16 @@
package test;
class C {
void foo(ClassWithStatics c) {
ClassWithStatics.staticMethod(ClassWithStatics.staticField);
c.instanceMethod();
ClassWithStatics.staticField += 2;
}
}
class D extends ClassWithStatics {
void foo() {
staticMethod(staticField);
ourValue *= 2;
}
}
@@ -0,0 +1,16 @@
package test;
class C {
void foo(ClassWithStatics c) {
ClassWithStatics.Companion.staticMethod(ClassWithStatics.staticField);
c.instanceMethod();
ClassWithStatics.staticField += 2;
}
}
class D extends ClassWithStatics {
void foo() {
Companion.staticMethod(staticField);
value *= 2;
}
}
@@ -0,0 +1,5 @@
package test
fun foo() {
ClassWithStatics.staticMethod(ClassWithStatics.staticField)
}
@@ -0,0 +1,5 @@
package test
fun foo() {
ClassWithStatics.staticMethod(ClassWithStatics.staticField)
}
@@ -0,0 +1,6 @@
package test;
class PureUtils {
public static void foo1(int p) {}
public static int foo2() { return 1; }
}
@@ -0,0 +1,10 @@
package test
object PureUtils {
public fun foo1(p: Int) {
}
public fun foo2(): Int {
return 1
}
}
@@ -0,0 +1,8 @@
package test;
class Utils {
public static void foo1(int p) {}
public static int foo2() { return 1; }
public static final int staticField = 1;
}
+12
View File
@@ -0,0 +1,12 @@
package test
object Utils {
public fun foo1(p: Int) {
}
public fun foo2(): Int {
return 1
}
public val staticField: Int = 1
}
@@ -0,0 +1,9 @@
package test;
class C {
void foo() {
Utils.foo1(Utils.staticField);
Utils.staticField += Utils.foo2();
PureUtils.foo1(PureUtils.foo2())
}
}
@@ -0,0 +1,9 @@
package test;
class C {
void foo() {
Utils.INSTANCE$.foo1(Utils.staticField);
Utils.staticField += Utils.INSTANCE$.foo2();
PureUtils.INSTANCE$.foo1(PureUtils.INSTANCE$.foo2())
}
}
@@ -0,0 +1,6 @@
package test
fun foo() {
Utils.foo1(Utils.staticField)
PureUtils.foo1(PureUtils.foo2())
}
@@ -0,0 +1,6 @@
package test
fun foo() {
Utils.foo1(Utils.staticField)
PureUtils.foo1(PureUtils.foo2())
}
@@ -16,49 +16,74 @@
package org.jetbrains.kotlin.j2k package org.jetbrains.kotlin.j2k
import java.io.File import com.intellij.openapi.progress.EmptyProgressIndicator
import com.intellij.testFramework.LightPlatformTestCase import com.intellij.psi.PsiFile
import com.intellij.psi.codeStyle.CodeStyleManager
import org.jetbrains.kotlin.test.JetTestUtils
import com.intellij.openapi.project.Project
import com.intellij.openapi.command.WriteCommandAction
import org.jetbrains.kotlin.idea.j2k.J2kPostProcessor
import org.jetbrains.kotlin.idea.JetWithJdkAndRuntimeLightProjectDescriptor
import com.intellij.psi.PsiJavaFile import com.intellij.psi.PsiJavaFile
import com.intellij.psi.PsiManager import com.intellij.psi.PsiManager
import java.util.ArrayList import com.intellij.testFramework.LightPlatformTestCase
import org.jetbrains.kotlin.psi.JetFile import org.jetbrains.kotlin.idea.JetWithJdkAndRuntimeLightProjectDescriptor
import org.jetbrains.kotlin.idea.j2k.IdeaResolverForConverter import org.jetbrains.kotlin.idea.j2k.IdeaResolverForConverter
import org.jetbrains.kotlin.idea.j2k.J2kPostProcessor
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.psi.JetFile
import org.jetbrains.kotlin.test.JetTestUtils
import java.io.File
import java.util.ArrayList
public abstract class AbstractJavaToKotlinConverterMultiFileTest : AbstractJavaToKotlinConverterTest() { public abstract class AbstractJavaToKotlinConverterMultiFileTest : AbstractJavaToKotlinConverterTest() {
public fun doTest(dirPath: String) { public fun doTest(dirPath: String) {
val project = LightPlatformTestCase.getProject()!! val project = LightPlatformTestCase.getProject()!!
val psiManager = PsiManager.getInstance(project) val psiManager = PsiManager.getInstance(project)
val javaFiles = File(dirPath).listFiles { file, name -> name.endsWith(".java") } val filesToConvert = File(dirPath).listFiles { file, name -> name.endsWith(".java") }
val psiFiles = ArrayList<PsiJavaFile>() val psiFilesToConvert = ArrayList<PsiJavaFile>()
for (javaFile: File in javaFiles) { for (javaFile in filesToConvert) {
val virtualFile = addFile(javaFile, "test") val virtualFile = addFile(javaFile, "test")
val psiFile = psiManager.findFile(virtualFile) as PsiJavaFile val psiFile = psiManager.findFile(virtualFile) as PsiJavaFile
psiFiles.add(psiFile) psiFilesToConvert.add(psiFile)
}
val externalFiles = File(dirPath + File.separator + "external").listFiles { file, name -> name.endsWith(".java") || name.endsWith(".kt") }
val externalPsiFiles = ArrayList<PsiFile>()
for (file in externalFiles) {
val virtualFile = addFile(file, "test")
val psiFile = psiManager.findFile(virtualFile)!!
externalPsiFiles.add(psiFile)
assert(psiFile is PsiJavaFile || psiFile is JetFile)
} }
val converter = JavaToKotlinConverter(project, ConverterSettings.defaultSettings, val converter = JavaToKotlinConverter(project, ConverterSettings.defaultSettings,
IdeaReferenceSearcher, IdeaResolverForConverter, J2kPostProcessor(formatCode = true)) IdeaReferenceSearcher, IdeaResolverForConverter, J2kPostProcessor(formatCode = true))
val inputElements = psiFiles.map { JavaToKotlinConverter.InputElement(it, it) } val inputElements = psiFilesToConvert.map { JavaToKotlinConverter.InputElement(it, it) }
val results: List<String> = converter.elementsToKotlin(inputElements).map { it!!.text } val (results, externalCodeProcessor) = converter.elementsToKotlin(inputElements)
fun expectedFile(i: Int) = File(javaFiles[i].getPath().replace(".java", ".kt")) val process = externalCodeProcessor?.invoke(EmptyProgressIndicator())
project.executeWriteCommand("") { process?.invoke() }
val jetFiles = ArrayList<JetFile>() fun expectedResultFile(i: Int) = File(filesToConvert[i].getPath().replace(".java", ".kt"))
for (i in javaFiles.indices) {
deleteFile(psiFiles[i].getVirtualFile()) val resultFiles = ArrayList<JetFile>()
val virtualFile = addFile(results[i], expectedFile(i).getName(), "test") for ((i, javaFile) in psiFilesToConvert.withIndex()) {
jetFiles.add(psiManager.findFile(virtualFile) as JetFile) deleteFile(javaFile.getVirtualFile())
val virtualFile = addFile(results.map { it!!.text }[i], expectedResultFile(i).getName(), "test")
resultFiles.add(psiManager.findFile(virtualFile) as JetFile)
} }
for ((i, jetFile) in jetFiles.withIndex()) { for ((i, kotlinFile) in resultFiles.withIndex()) {
JetTestUtils.assertEqualsToFile(expectedFile(i), addErrorsDump(jetFile)) JetTestUtils.assertEqualsToFile(expectedResultFile(i), addErrorsDump(kotlinFile))
}
for ((externalFile, externalPsiFile) in externalFiles.zip(externalPsiFiles)) {
val expectedFile = File(externalFile.getPath() + ".expected")
var resultText = if (externalPsiFile is JetFile) {
addErrorsDump(externalPsiFile)
}
else {
//TODO: errors dump for java files too
externalPsiFile.getText()
}
JetTestUtils.assertEqualsToFile(expectedFile, resultText)
} }
} }
@@ -109,7 +109,7 @@ public abstract class AbstractJavaToKotlinConverterSingleFileTest : AbstractJava
val converter = JavaToKotlinConverter(project, settings, val converter = JavaToKotlinConverter(project, settings,
IdeaReferenceSearcher, IdeaResolverForConverter, J2kPostProcessor(formatCode = false)) IdeaReferenceSearcher, IdeaResolverForConverter, J2kPostProcessor(formatCode = false))
val inputElements = listOf(JavaToKotlinConverter.InputElement(file, file)) val inputElements = listOf(JavaToKotlinConverter.InputElement(file, file))
return converter.elementsToKotlin(inputElements).single()!!.text return converter.elementsToKotlin(inputElements).results.single()!!.text
} }
private fun methodToKotlin(text: String, settings: ConverterSettings, project: Project): String { private fun methodToKotlin(text: String, settings: ConverterSettings, project: Project): String {
@@ -45,8 +45,8 @@ public abstract class AbstractJavaToKotlinConverterTest : LightCodeInsightFixtur
super.tearDown() super.tearDown()
} }
private fun addFile(fileName: String, packageName: String) { private fun addFile(fileName: String, dirName: String) {
addFile(File("j2k/testData/$fileName"), packageName) addFile(File("j2k/testData/$fileName"), dirName)
} }
protected fun addFile(file: File, dirName: String): VirtualFile { protected fun addFile(file: File, dirName: String): VirtualFile {
@@ -58,7 +58,7 @@ public abstract class AbstractJavaToKotlinConverterTest : LightCodeInsightFixtur
override fun compute(): VirtualFile? { override fun compute(): VirtualFile? {
val root = LightPlatformTestCase.getSourceRoot()!! val root = LightPlatformTestCase.getSourceRoot()!!
val virtualDir = root.findChild(dirName) ?: root.createChildDirectory(null, dirName) val virtualDir = root.findChild(dirName) ?: root.createChildDirectory(null, dirName)
val virtualFile = virtualDir.createChildData(null, fileName)!! val virtualFile = virtualDir.createChildData(null, fileName)
virtualFile.getOutputStream(null)!!.writer().use { it.write(text) } virtualFile.getOutputStream(null)!!.writer().use { it.write(text) }
return virtualFile return virtualFile
} }
@@ -1616,6 +1616,18 @@ public class JavaToKotlinConverterForWebDemoTestGenerated extends AbstractJavaTo
doTest(fileName); doTest(fileName);
} }
@TestMetadata("InCompanionObject.java")
public void testInCompanionObject() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/dropAccessors/InCompanionObject.java");
doTest(fileName);
}
@TestMetadata("InObject.java")
public void testInObject() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/dropAccessors/InObject.java");
doTest(fileName);
}
@TestMetadata("JavaKeywordPropertyName.java") @TestMetadata("JavaKeywordPropertyName.java")
public void testJavaKeywordPropertyName() throws Exception { public void testJavaKeywordPropertyName() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/dropAccessors/JavaKeywordPropertyName.java"); String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/dropAccessors/JavaKeywordPropertyName.java");
@@ -30,12 +30,16 @@ import java.util.regex.Pattern;
@SuppressWarnings("all") @SuppressWarnings("all")
@TestMetadata("j2k/testData/multiFile") @TestMetadata("j2k/testData/multiFile")
@TestDataPath("$PROJECT_ROOT") @TestDataPath("$PROJECT_ROOT")
@InnerTestClasses({
})
@RunWith(JUnit3RunnerWithInners.class) @RunWith(JUnit3RunnerWithInners.class)
public class JavaToKotlinConverterMultiFileTestGenerated extends AbstractJavaToKotlinConverterMultiFileTest { public class JavaToKotlinConverterMultiFileTestGenerated extends AbstractJavaToKotlinConverterMultiFileTest {
public void testAllFilesPresentInMultiFile() throws Exception { public void testAllFilesPresentInMultiFile() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("j2k/testData/multiFile"), Pattern.compile("^([^\\.]+)$"), true); JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("j2k/testData/multiFile"), Pattern.compile("^([^\\.]+)$"), false);
}
@TestMetadata("FieldToProperty")
public void testFieldToProperty() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/multiFile/FieldToProperty/");
doTest(fileName);
} }
@TestMetadata("GetterAndSetterUsages") @TestMetadata("GetterAndSetterUsages")
@@ -44,4 +48,15 @@ public class JavaToKotlinConverterMultiFileTestGenerated extends AbstractJavaToK
doTest(fileName); doTest(fileName);
} }
@TestMetadata("ToCompanionObject")
public void testToCompanionObject() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/multiFile/ToCompanionObject/");
doTest(fileName);
}
@TestMetadata("ToObject")
public void testToObject() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/multiFile/ToObject/");
doTest(fileName);
}
} }
@@ -1616,6 +1616,18 @@ public class JavaToKotlinConverterSingleFileTestGenerated extends AbstractJavaTo
doTest(fileName); doTest(fileName);
} }
@TestMetadata("InCompanionObject.java")
public void testInCompanionObject() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/dropAccessors/InCompanionObject.java");
doTest(fileName);
}
@TestMetadata("InObject.java")
public void testInObject() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/dropAccessors/InObject.java");
doTest(fileName);
}
@TestMetadata("JavaKeywordPropertyName.java") @TestMetadata("JavaKeywordPropertyName.java")
public void testJavaKeywordPropertyName() throws Exception { public void testJavaKeywordPropertyName() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/dropAccessors/JavaKeywordPropertyName.java"); String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/dropAccessors/JavaKeywordPropertyName.java");