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
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()
}
+118 -107
View File
@@ -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
+1 -4
View File
@@ -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))
}