Java to Kotlin conversion: minor refactorings after code review
This commit is contained in:
@@ -16,20 +16,8 @@
|
||||
|
||||
package org.jetbrains.jet.j2k
|
||||
|
||||
import com.intellij.psi.PsiMethod
|
||||
import com.intellij.psi.PsiClass
|
||||
import com.intellij.psi.PsiReferenceExpression
|
||||
import com.intellij.psi.JavaRecursiveElementVisitor
|
||||
import com.intellij.psi.*
|
||||
import java.util.LinkedHashSet
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiMethodCallExpression
|
||||
import com.intellij.psi.PsiParameter
|
||||
import com.intellij.psi.PsiField
|
||||
import com.intellij.psi.PsiAssignmentExpression
|
||||
import com.intellij.psi.PsiThisExpression
|
||||
import com.intellij.psi.PsiStatement
|
||||
import com.intellij.psi.PsiExpressionStatement
|
||||
import com.intellij.psi.PsiBlockStatement
|
||||
import com.intellij.psi.util.PsiUtil
|
||||
|
||||
fun PsiMethod.isPrimaryConstructor(): Boolean {
|
||||
@@ -48,6 +36,7 @@ fun PsiClass.getPrimaryConstructor(): PsiMethod? {
|
||||
|
||||
else -> {
|
||||
// if there is more than one constructor then choose one invoked by all others
|
||||
//TODO: logic is incorrect - there can be a constructor which does not call any other
|
||||
class Visitor() : JavaRecursiveElementVisitor() {
|
||||
//TODO: skip all non-constructor members (optimization)
|
||||
private val invokedConstructors = LinkedHashSet<PsiMethod>()
|
||||
@@ -89,7 +78,7 @@ fun PsiElement.getContainingConstructor(): PsiMethod? {
|
||||
|
||||
fun PsiMethodCallExpression.isSuperConstructorCall(): Boolean {
|
||||
val ref = getMethodExpression()
|
||||
if (ref.getCanonicalText().equals("super")) {
|
||||
if (ref.getCanonicalText() == "super") {
|
||||
val target = ref.resolve()
|
||||
return target is PsiMethod && target.isConstructor()
|
||||
}
|
||||
|
||||
@@ -122,53 +122,7 @@ public class Converter(val project: Project, val settings: ConverterSettings) {
|
||||
|
||||
else -> {
|
||||
if (psiClass.getPrimaryConstructor() == null && psiClass.getConstructors().size > 1) {
|
||||
val finalOrWithEmptyInitializerFields = classBodyElements.filterIsInstance(javaClass<Field>()).filter { it.isVal() || it.initializer.toKotlin().isEmpty() }
|
||||
val initializers = HashMap<String, String>()
|
||||
for (element in classBodyElements) {
|
||||
if (element is SecondaryConstructor) {
|
||||
for (field in finalOrWithEmptyInitializerFields) {
|
||||
initializers.put(field.identifier.toKotlin(), getDefaultInitializer(field))
|
||||
}
|
||||
|
||||
val newStatements = ArrayList<Statement>()
|
||||
for (statement in element.block!!.statements) {
|
||||
var keepStatement = true
|
||||
if (statement is AssignmentExpression) {
|
||||
val assignee = statement.left
|
||||
if (assignee is CallChainExpression) {
|
||||
for (field in finalOrWithEmptyInitializerFields) {
|
||||
val id = field.identifier.toKotlin()
|
||||
if (assignee.identifier.toKotlin().endsWith("." + id)) {
|
||||
initializers.put(id, statement.right.toKotlin())
|
||||
keepStatement = false
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (keepStatement) {
|
||||
newStatements.add(statement)
|
||||
}
|
||||
|
||||
}
|
||||
newStatements.add(0, DummyStringExpression("val __ = " + createPrimaryConstructorInvocation(name.toKotlin(), finalOrWithEmptyInitializerFields, initializers)))
|
||||
element.block = Block(newStatements)
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: comments?
|
||||
val parameters = finalOrWithEmptyInitializerFields.map { field ->
|
||||
val varValModifier = if (field.modifiers.contains(Modifier.FINAL)) Parameter.VarValModifier.Val else Parameter.VarValModifier.Var
|
||||
Parameter(field.identifier, field.`type`, varValModifier, field.modifiers.filter { ACCESS_MODIFIERS.contains(it) })
|
||||
}
|
||||
classBodyElements.add(PrimaryConstructor(this,
|
||||
MemberComments.Empty,
|
||||
setOf(Modifier.PRIVATE),
|
||||
ParameterList(parameters),
|
||||
Block.Empty))
|
||||
classBodyElements.removeAll(finalOrWithEmptyInitializerFields)
|
||||
generateArtificialPrimaryConstructor(name, classBodyElements)
|
||||
}
|
||||
|
||||
val baseClassParams: List<Expression> = run {
|
||||
@@ -188,34 +142,54 @@ public class Converter(val project: Project, val settings: ConverterSettings) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun findBackingFieldForConstructorParameter(parameter: PsiParameter, constructor: PsiMethod): Pair<PsiField, PsiStatement>? {
|
||||
val body = constructor.getBody() ?: return null
|
||||
private fun generateArtificialPrimaryConstructor(className: Identifier, classBodyElements: MutableList<Element>) {
|
||||
val finalOrWithEmptyInitializerFields = classBodyElements.filterIsInstance(javaClass<Field>()).filter { it.isVal() || it.initializer.toKotlin().isEmpty() }
|
||||
val initializers = HashMap<String, String>()
|
||||
for (element in classBodyElements) {
|
||||
if (element is SecondaryConstructor) {
|
||||
for (field in finalOrWithEmptyInitializerFields) {
|
||||
initializers.put(field.identifier.toKotlin(), getDefaultInitializer(field))
|
||||
}
|
||||
|
||||
val refs = findExpressionReferences(parameter, body)
|
||||
val newStatements = ArrayList<Statement>()
|
||||
for (statement in element.block!!.statements) {
|
||||
var keepStatement = true
|
||||
if (statement is AssignmentExpression) {
|
||||
val assignee = statement.left
|
||||
if (assignee is CallChainExpression) {
|
||||
for (field in finalOrWithEmptyInitializerFields) {
|
||||
val id = field.identifier.toKotlin()
|
||||
if (assignee.identifier.toKotlin().endsWith("." + id)) {
|
||||
initializers.put(id, statement.right.toKotlin())
|
||||
keepStatement = false
|
||||
}
|
||||
|
||||
if (refs.any { PsiUtil.isAccessedForWriting(it) }) return null
|
||||
}
|
||||
}
|
||||
|
||||
for(ref in refs) {
|
||||
val assignment = ref.getParent() as? PsiAssignmentExpression ?: continue
|
||||
if (assignment.getOperationSign().getTokenType() != JavaTokenType.EQ) continue
|
||||
val assignee = assignment.getLExpression() as? PsiReferenceExpression ?: continue
|
||||
if (!isQualifierEmptyOrThis(assignee)) continue
|
||||
val field = assignee.resolve() as? PsiField ?: continue
|
||||
if (field.getContainingClass() != constructor.getContainingClass()) continue
|
||||
if (field.getInitializer() != null) continue
|
||||
}
|
||||
|
||||
// assignment should be a top-level statement
|
||||
val statement = assignment.getParent() as? PsiExpressionStatement ?: continue
|
||||
if (statement.getParent() != body) continue
|
||||
if (keepStatement) {
|
||||
newStatements.add(statement)
|
||||
}
|
||||
|
||||
// and no other assignments to field should exist in the constructor
|
||||
if (findExpressionReferences(field, body).any { it != assignee && PsiUtil.isAccessedForWriting(it) && isQualifierEmptyOrThis(it) }) continue
|
||||
//TODO: check access to field before assignment
|
||||
|
||||
return field to statement
|
||||
}
|
||||
newStatements.add(0, DummyStringExpression("val __ = " + createPrimaryConstructorInvocation(className.toKotlin(), finalOrWithEmptyInitializerFields, initializers)))
|
||||
element.block = Block(newStatements)
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
//TODO: comments?
|
||||
val parameters = finalOrWithEmptyInitializerFields.map { field ->
|
||||
val varValModifier = if (field.modifiers.contains(Modifier.FINAL)) Parameter.VarValModifier.Val else Parameter.VarValModifier.Var
|
||||
Parameter(field.identifier, field.`type`, varValModifier, field.modifiers.filter { ACCESS_MODIFIERS.contains(it) })
|
||||
}
|
||||
classBodyElements.add(PrimaryConstructor(this,
|
||||
MemberComments.Empty,
|
||||
setOf(Modifier.PRIVATE),
|
||||
ParameterList(parameters),
|
||||
Block.Empty))
|
||||
classBodyElements.removeAll(finalOrWithEmptyInitializerFields)
|
||||
}
|
||||
|
||||
private fun convertInitializer(initializer: PsiClassInitializer): Initializer {
|
||||
@@ -283,45 +257,7 @@ public class Converter(val project: Project, val settings: ConverterSettings) {
|
||||
|
||||
if (method.isConstructor()) {
|
||||
if (method.isPrimaryConstructor()) {
|
||||
val params = method.getParameterList().getParameters()
|
||||
val parameterToField = HashMap<PsiParameter, PsiField>()
|
||||
val body = method.getBody()
|
||||
val block = if (body != null) {
|
||||
val statementsToRemove = HashSet<PsiStatement>()
|
||||
val usageReplacementMap = HashMap<PsiVariable, String>()
|
||||
for (parameter in params) {
|
||||
val (field, initializationStatement) = findBackingFieldForConstructorParameter(parameter, method) ?: continue
|
||||
if (membersToRemove.contains(field)) continue // already used as backing field
|
||||
if (convertVariableType(field) != convertVariableType(parameter)) continue
|
||||
|
||||
parameterToField.put(parameter, field)
|
||||
statementsToRemove.add(initializationStatement)
|
||||
|
||||
if (field.getName() != parameter.getName()) {
|
||||
usageReplacementMap.put(parameter, field.getName()!!)
|
||||
}
|
||||
}
|
||||
dispatcher.expressionVisitor = ExpressionVisitor(this, usageReplacementMap)
|
||||
Block(convertStatements(body.getStatements().filter{ !statementsToRemove.contains(it) }), false)
|
||||
}
|
||||
else {
|
||||
Block.Empty
|
||||
}
|
||||
|
||||
val parameterList = ParameterList(params.map {
|
||||
val field = parameterToField[it]
|
||||
if (field == null) {
|
||||
convertParameter(it)
|
||||
}
|
||||
else {
|
||||
membersToRemove.add(field)
|
||||
Parameter(Identifier(field.getName()!!),
|
||||
convertVariableType(it),
|
||||
if (field.hasModifierProperty(PsiModifier.FINAL)) Parameter.VarValModifier.Val else Parameter.VarValModifier.Var,
|
||||
convertModifierList(field.getModifierList()).filter { ACCESS_MODIFIERS.contains(it) })
|
||||
}
|
||||
})
|
||||
return PrimaryConstructor(this, comments, modifiers, parameterList, block)
|
||||
return convertPrimaryConstructor(method, modifiers, comments, membersToRemove)
|
||||
}
|
||||
else {
|
||||
val params = convertParameterList(method.getParameterList())
|
||||
@@ -340,6 +276,81 @@ public class Converter(val project: Project, val settings: ConverterSettings) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun convertPrimaryConstructor(constructor: PsiMethod,
|
||||
modifiers: Set<Modifier>,
|
||||
comments: MemberComments,
|
||||
membersToRemove: MutableSet<PsiMember>): PrimaryConstructor {
|
||||
val params = constructor.getParameterList().getParameters()
|
||||
val parameterToField = HashMap<PsiParameter, PsiField>()
|
||||
val body = constructor.getBody()
|
||||
val block = if (body != null) {
|
||||
val statementsToRemove = HashSet<PsiStatement>()
|
||||
val usageReplacementMap = HashMap<PsiVariable, String>()
|
||||
for (parameter in params) {
|
||||
val (field, initializationStatement) = findBackingFieldForConstructorParameter(parameter, constructor) ?: continue
|
||||
if (convertVariableType(field) != convertVariableType(parameter)) continue
|
||||
|
||||
parameterToField.put(parameter, field)
|
||||
statementsToRemove.add(initializationStatement)
|
||||
membersToRemove.add(field)
|
||||
|
||||
if (field.getName() != parameter.getName()) {
|
||||
usageReplacementMap.put(parameter, field.getName()!!)
|
||||
}
|
||||
}
|
||||
dispatcher.expressionVisitor = ExpressionVisitor(this, usageReplacementMap)
|
||||
Block(convertStatements(body.getStatements().filter{ !statementsToRemove.contains(it) }), false)
|
||||
}
|
||||
else {
|
||||
Block.Empty
|
||||
}
|
||||
|
||||
val parameterList = ParameterList(params.map {
|
||||
val field = parameterToField[it]
|
||||
if (field == null) {
|
||||
convertParameter(it)
|
||||
}
|
||||
else {
|
||||
Parameter(Identifier(field.getName()!!),
|
||||
convertVariableType(it),
|
||||
if (field.hasModifierProperty(PsiModifier.FINAL)) Parameter.VarValModifier.Val else Parameter.VarValModifier.Var,
|
||||
convertModifierList(field.getModifierList()).filter { ACCESS_MODIFIERS.contains(it) })
|
||||
}
|
||||
})
|
||||
return PrimaryConstructor(this, comments, modifiers, parameterList, block)
|
||||
}
|
||||
|
||||
private fun findBackingFieldForConstructorParameter(parameter: PsiParameter, constructor: PsiMethod): Pair<PsiField, PsiStatement>? {
|
||||
val body = constructor.getBody() ?: return null
|
||||
|
||||
val refs = findExpressionReferences(parameter, body)
|
||||
|
||||
if (refs.any { PsiUtil.isAccessedForWriting(it) }) return null
|
||||
|
||||
for(ref in refs) {
|
||||
val assignment = ref.getParent() as? PsiAssignmentExpression ?: continue
|
||||
if (assignment.getOperationSign().getTokenType() != JavaTokenType.EQ) continue
|
||||
val assignee = assignment.getLExpression() as? PsiReferenceExpression ?: continue
|
||||
if (!isQualifierEmptyOrThis(assignee)) continue
|
||||
val field = assignee.resolve() as? PsiField ?: continue
|
||||
if (field.getContainingClass() != constructor.getContainingClass()) continue
|
||||
if (field.hasModifierProperty(PsiModifier.STATIC)) continue
|
||||
if (field.getInitializer() != null) continue
|
||||
|
||||
// assignment should be a top-level statement
|
||||
val statement = assignment.getParent() as? PsiExpressionStatement ?: continue
|
||||
if (statement.getParent() != body) continue
|
||||
|
||||
// and no other assignments to field should exist in the constructor
|
||||
if (findExpressionReferences(field, body).any { it != assignee && PsiUtil.isAccessedForWriting(it) && isQualifierEmptyOrThis(it) }) continue
|
||||
//TODO: check access to field before assignment
|
||||
|
||||
return field to statement
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
public fun convertBlock(block: PsiCodeBlock?): Block {
|
||||
if (block == null) return Block.Empty
|
||||
|
||||
|
||||
@@ -48,10 +48,7 @@ open class Field(
|
||||
val declaration: String = commentsToKotlin() +
|
||||
modifiersToKotlin() + identifier.toKotlin() + " : " + `type`.toKotlin()
|
||||
if (initializer.isEmpty) {
|
||||
return declaration + ((if (isVal() && !isStatic() && writingAccesses != 0)
|
||||
""
|
||||
else
|
||||
" = " + getDefaultInitializer(this)))
|
||||
return declaration + (if (isVal() && !isStatic() && writingAccesses != 0) "" else " = " + getDefaultInitializer(this))
|
||||
}
|
||||
|
||||
return declaration + " = " + initializer.toKotlin()
|
||||
|
||||
@@ -80,11 +80,11 @@ class StatementVisitor(public val converter: Converter) : JavaElementVisitor() {
|
||||
val condition = statement.getCondition()
|
||||
val body = statement.getBody()
|
||||
|
||||
val initializationVar = initialization?.getFirstChild() as? PsiLocalVariable
|
||||
val onceWritableIterator = initializationVar != null
|
||||
&& initializationVar.countWriteAccesses(body) == 0
|
||||
&& initializationVar.countWriteAccesses(condition) == 0
|
||||
&& initializationVar.countWriteAccesses(update) == 1
|
||||
val loopVar = initialization?.getFirstChild() as? PsiLocalVariable
|
||||
val onceWritableIterator = loopVar != null
|
||||
&& loopVar.countWriteAccesses(body) == 0
|
||||
&& loopVar.countWriteAccesses(condition) == 0
|
||||
&& loopVar.countWriteAccesses(update) == 1
|
||||
|
||||
val operationTokenType = (condition as? PsiBinaryExpression)?.getOperationTokenType()
|
||||
if (initialization is PsiDeclarationStatement
|
||||
@@ -94,16 +94,16 @@ class StatementVisitor(public val converter: Converter) : JavaElementVisitor() {
|
||||
&& update.getChildren().size == 1
|
||||
&& isPlusPlusExpression(update.getChildren().single())
|
||||
&& (operationTokenType == JavaTokenType.LT || operationTokenType == JavaTokenType.LE)
|
||||
&& initializationVar != null
|
||||
&& initializationVar.getNameIdentifier() != null
|
||||
&& loopVar != null
|
||||
&& loopVar.getNameIdentifier() != null
|
||||
&& onceWritableIterator) {
|
||||
val end = converter.convertExpression((condition as PsiBinaryExpression).getROperand())
|
||||
val endExpression = if (operationTokenType == JavaTokenType.LT)
|
||||
BinaryExpression(end, Identifier("1"), "-")
|
||||
else
|
||||
end
|
||||
result = ForeachWithRangeStatement(Identifier(initializationVar.getName()!!),
|
||||
converter.convertExpression(initializationVar.getInitializer()),
|
||||
result = ForeachWithRangeStatement(Identifier(loopVar.getName()!!),
|
||||
converter.convertExpression(loopVar.getInitializer()),
|
||||
endExpression,
|
||||
converter.convertStatement(body))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user