Java to Kotlin conversion: minor refactorings after code review

This commit is contained in:
Valentin Kipyatkov
2014-06-03 17:50:32 +04:00
parent ab45439256
commit 57d118e13d
4 changed files with 131 additions and 134 deletions
@@ -16,20 +16,8 @@
package org.jetbrains.jet.j2k package org.jetbrains.jet.j2k
import com.intellij.psi.PsiMethod import com.intellij.psi.*
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiReferenceExpression
import com.intellij.psi.JavaRecursiveElementVisitor
import java.util.LinkedHashSet 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 import com.intellij.psi.util.PsiUtil
fun PsiMethod.isPrimaryConstructor(): Boolean { fun PsiMethod.isPrimaryConstructor(): Boolean {
@@ -48,6 +36,7 @@ fun PsiClass.getPrimaryConstructor(): PsiMethod? {
else -> { else -> {
// if there is more than one constructor then choose one invoked by all others // 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() { class Visitor() : JavaRecursiveElementVisitor() {
//TODO: skip all non-constructor members (optimization) //TODO: skip all non-constructor members (optimization)
private val invokedConstructors = LinkedHashSet<PsiMethod>() private val invokedConstructors = LinkedHashSet<PsiMethod>()
@@ -89,7 +78,7 @@ fun PsiElement.getContainingConstructor(): PsiMethod? {
fun PsiMethodCallExpression.isSuperConstructorCall(): Boolean { fun PsiMethodCallExpression.isSuperConstructorCall(): Boolean {
val ref = getMethodExpression() val ref = getMethodExpression()
if (ref.getCanonicalText().equals("super")) { if (ref.getCanonicalText() == "super") {
val target = ref.resolve() val target = ref.resolve()
return target is PsiMethod && target.isConstructor() return target is PsiMethod && target.isConstructor()
} }
+118 -107
View File
@@ -122,53 +122,7 @@ public class Converter(val project: Project, val settings: ConverterSettings) {
else -> { else -> {
if (psiClass.getPrimaryConstructor() == null && psiClass.getConstructors().size > 1) { if (psiClass.getPrimaryConstructor() == null && psiClass.getConstructors().size > 1) {
val finalOrWithEmptyInitializerFields = classBodyElements.filterIsInstance(javaClass<Field>()).filter { it.isVal() || it.initializer.toKotlin().isEmpty() } generateArtificialPrimaryConstructor(name, classBodyElements)
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)
} }
val baseClassParams: List<Expression> = run { 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>? { private fun generateArtificialPrimaryConstructor(className: Identifier, classBodyElements: MutableList<Element>) {
val body = constructor.getBody() ?: return null 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 if (keepStatement) {
val statement = assignment.getParent() as? PsiExpressionStatement ?: continue newStatements.add(statement)
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 newStatements.add(0, DummyStringExpression("val __ = " + createPrimaryConstructorInvocation(className.toKotlin(), finalOrWithEmptyInitializerFields, initializers)))
//TODO: check access to field before assignment element.block = Block(newStatements)
}
return field to statement
} }
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 { private fun convertInitializer(initializer: PsiClassInitializer): Initializer {
@@ -283,45 +257,7 @@ public class Converter(val project: Project, val settings: ConverterSettings) {
if (method.isConstructor()) { if (method.isConstructor()) {
if (method.isPrimaryConstructor()) { if (method.isPrimaryConstructor()) {
val params = method.getParameterList().getParameters() return convertPrimaryConstructor(method, modifiers, comments, membersToRemove)
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)
} }
else { else {
val params = convertParameterList(method.getParameterList()) 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 { public fun convertBlock(block: PsiCodeBlock?): Block {
if (block == null) return Block.Empty if (block == null) return Block.Empty
+1 -4
View File
@@ -48,10 +48,7 @@ open class Field(
val declaration: String = commentsToKotlin() + val declaration: String = commentsToKotlin() +
modifiersToKotlin() + identifier.toKotlin() + " : " + `type`.toKotlin() modifiersToKotlin() + identifier.toKotlin() + " : " + `type`.toKotlin()
if (initializer.isEmpty) { if (initializer.isEmpty) {
return declaration + ((if (isVal() && !isStatic() && writingAccesses != 0) return declaration + (if (isVal() && !isStatic() && writingAccesses != 0) "" else " = " + getDefaultInitializer(this))
""
else
" = " + getDefaultInitializer(this)))
} }
return declaration + " = " + initializer.toKotlin() return declaration + " = " + initializer.toKotlin()
@@ -80,11 +80,11 @@ class StatementVisitor(public val converter: Converter) : JavaElementVisitor() {
val condition = statement.getCondition() val condition = statement.getCondition()
val body = statement.getBody() val body = statement.getBody()
val initializationVar = initialization?.getFirstChild() as? PsiLocalVariable val loopVar = initialization?.getFirstChild() as? PsiLocalVariable
val onceWritableIterator = initializationVar != null val onceWritableIterator = loopVar != null
&& initializationVar.countWriteAccesses(body) == 0 && loopVar.countWriteAccesses(body) == 0
&& initializationVar.countWriteAccesses(condition) == 0 && loopVar.countWriteAccesses(condition) == 0
&& initializationVar.countWriteAccesses(update) == 1 && loopVar.countWriteAccesses(update) == 1
val operationTokenType = (condition as? PsiBinaryExpression)?.getOperationTokenType() val operationTokenType = (condition as? PsiBinaryExpression)?.getOperationTokenType()
if (initialization is PsiDeclarationStatement if (initialization is PsiDeclarationStatement
@@ -94,16 +94,16 @@ class StatementVisitor(public val converter: Converter) : JavaElementVisitor() {
&& update.getChildren().size == 1 && update.getChildren().size == 1
&& isPlusPlusExpression(update.getChildren().single()) && isPlusPlusExpression(update.getChildren().single())
&& (operationTokenType == JavaTokenType.LT || operationTokenType == JavaTokenType.LE) && (operationTokenType == JavaTokenType.LT || operationTokenType == JavaTokenType.LE)
&& initializationVar != null && loopVar != null
&& initializationVar.getNameIdentifier() != null && loopVar.getNameIdentifier() != null
&& onceWritableIterator) { && onceWritableIterator) {
val end = converter.convertExpression((condition as PsiBinaryExpression).getROperand()) val end = converter.convertExpression((condition as PsiBinaryExpression).getROperand())
val endExpression = if (operationTokenType == JavaTokenType.LT) val endExpression = if (operationTokenType == JavaTokenType.LT)
BinaryExpression(end, Identifier("1"), "-") BinaryExpression(end, Identifier("1"), "-")
else else
end end
result = ForeachWithRangeStatement(Identifier(initializationVar.getName()!!), result = ForeachWithRangeStatement(Identifier(loopVar.getName()!!),
converter.convertExpression(initializationVar.getInitializer()), converter.convertExpression(loopVar.getInitializer()),
endExpression, endExpression,
converter.convertStatement(body)) converter.convertStatement(body))
} }