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()
} }
+78 -67
View File
@@ -122,6 +122,27 @@ 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) {
generateArtificialPrimaryConstructor(name, classBodyElements)
}
val baseClassParams: List<Expression> = run {
val superVisitor = SuperVisitor()
psiClass.accept(superVisitor)
val resolvedSuperCallParameters = superVisitor.resolvedSuperCallParameters
if (resolvedSuperCallParameters.size() == 1) {
convertExpressions(resolvedSuperCallParameters.single().getExpressions())
}
else {
listOf()
}
}
return Class(this, name, getComments(psiClass), modifiers, typeParameters, extendsTypes, baseClassParams, implementsTypes, classBodyElements)
}
}
}
private fun generateArtificialPrimaryConstructor(className: Identifier, classBodyElements: MutableList<Element>) {
val finalOrWithEmptyInitializerFields = classBodyElements.filterIsInstance(javaClass<Field>()).filter { it.isVal() || it.initializer.toKotlin().isEmpty() } val finalOrWithEmptyInitializerFields = classBodyElements.filterIsInstance(javaClass<Field>()).filter { it.isVal() || it.initializer.toKotlin().isEmpty() }
val initializers = HashMap<String, String>() val initializers = HashMap<String, String>()
for (element in classBodyElements) { for (element in classBodyElements) {
@@ -153,7 +174,7 @@ public class Converter(val project: Project, val settings: ConverterSettings) {
} }
} }
newStatements.add(0, DummyStringExpression("val __ = " + createPrimaryConstructorInvocation(name.toKotlin(), finalOrWithEmptyInitializerFields, initializers))) newStatements.add(0, DummyStringExpression("val __ = " + createPrimaryConstructorInvocation(className.toKotlin(), finalOrWithEmptyInitializerFields, initializers)))
element.block = Block(newStatements) element.block = Block(newStatements)
} }
} }
@@ -171,53 +192,6 @@ public class Converter(val project: Project, val settings: ConverterSettings) {
classBodyElements.removeAll(finalOrWithEmptyInitializerFields) classBodyElements.removeAll(finalOrWithEmptyInitializerFields)
} }
val baseClassParams: List<Expression> = run {
val superVisitor = SuperVisitor()
psiClass.accept(superVisitor)
val resolvedSuperCallParameters = superVisitor.resolvedSuperCallParameters
if (resolvedSuperCallParameters.size() == 1) {
convertExpressions(resolvedSuperCallParameters.single().getExpressions())
}
else {
listOf()
}
}
return Class(this, name, getComments(psiClass), modifiers, typeParameters, extendsTypes, baseClassParams, implementsTypes, classBodyElements)
}
}
}
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.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
}
private fun convertInitializer(initializer: PsiClassInitializer): Initializer { private fun convertInitializer(initializer: PsiClassInitializer): Initializer {
return Initializer(convertBlock(initializer.getBody()), convertModifierList(initializer.getModifierList())) return Initializer(convertBlock(initializer.getBody()), convertModifierList(initializer.getModifierList()))
} }
@@ -283,19 +257,42 @@ 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)
}
else {
val params = convertParameterList(method.getParameterList())
return SecondaryConstructor(this, comments, modifiers, params, convertBlock(method.getBody()))
}
}
else {
val params = convertParameterList(method.getParameterList())
val typeParameterList = convertTypeParameterList(method.getTypeParameterList())
val block = convertBlock(method.getBody())
return Function(this, Identifier(method.getName()), comments, modifiers, returnType, typeParameterList, params, block)
}
}
finally {
dispatcher.expressionVisitor = ExpressionVisitor(this)
}
}
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 parameterToField = HashMap<PsiParameter, PsiField>()
val body = method.getBody() val body = constructor.getBody()
val block = if (body != null) { val block = if (body != null) {
val statementsToRemove = HashSet<PsiStatement>() val statementsToRemove = HashSet<PsiStatement>()
val usageReplacementMap = HashMap<PsiVariable, String>() val usageReplacementMap = HashMap<PsiVariable, String>()
for (parameter in params) { for (parameter in params) {
val (field, initializationStatement) = findBackingFieldForConstructorParameter(parameter, method) ?: continue val (field, initializationStatement) = findBackingFieldForConstructorParameter(parameter, constructor) ?: continue
if (membersToRemove.contains(field)) continue // already used as backing field
if (convertVariableType(field) != convertVariableType(parameter)) continue if (convertVariableType(field) != convertVariableType(parameter)) continue
parameterToField.put(parameter, field) parameterToField.put(parameter, field)
statementsToRemove.add(initializationStatement) statementsToRemove.add(initializationStatement)
membersToRemove.add(field)
if (field.getName() != parameter.getName()) { if (field.getName() != parameter.getName()) {
usageReplacementMap.put(parameter, field.getName()!!) usageReplacementMap.put(parameter, field.getName()!!)
@@ -314,7 +311,6 @@ public class Converter(val project: Project, val settings: ConverterSettings) {
convertParameter(it) convertParameter(it)
} }
else { else {
membersToRemove.add(field)
Parameter(Identifier(field.getName()!!), Parameter(Identifier(field.getName()!!),
convertVariableType(it), convertVariableType(it),
if (field.hasModifierProperty(PsiModifier.FINAL)) Parameter.VarValModifier.Val else Parameter.VarValModifier.Var, if (field.hasModifierProperty(PsiModifier.FINAL)) Parameter.VarValModifier.Val else Parameter.VarValModifier.Var,
@@ -323,21 +319,36 @@ public class Converter(val project: Project, val settings: ConverterSettings) {
}) })
return PrimaryConstructor(this, comments, modifiers, parameterList, block) return PrimaryConstructor(this, comments, modifiers, parameterList, block)
} }
else {
val params = convertParameterList(method.getParameterList()) private fun findBackingFieldForConstructorParameter(parameter: PsiParameter, constructor: PsiMethod): Pair<PsiField, PsiStatement>? {
return SecondaryConstructor(this, comments, modifiers, params, convertBlock(method.getBody())) val body = constructor.getBody() ?: return null
}
} val refs = findExpressionReferences(parameter, body)
else {
val params = convertParameterList(method.getParameterList()) if (refs.any { PsiUtil.isAccessedForWriting(it) }) return null
val typeParameterList = convertTypeParameterList(method.getTypeParameterList())
val block = convertBlock(method.getBody()) for(ref in refs) {
return Function(this, Identifier(method.getName()), comments, modifiers, returnType, typeParameterList, params, block) val assignment = ref.getParent() as? PsiAssignmentExpression ?: continue
} if (assignment.getOperationSign().getTokenType() != JavaTokenType.EQ) continue
} val assignee = assignment.getLExpression() as? PsiReferenceExpression ?: continue
finally { if (!isQualifierEmptyOrThis(assignee)) continue
dispatcher.expressionVisitor = ExpressionVisitor(this) 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 {
+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))
} }