Java to Kotlin converter: code refactorings after review

This commit is contained in:
Valentin Kipyatkov
2014-07-10 21:22:18 +04:00
parent ec6b158c0e
commit 276d1dcf14
9 changed files with 354 additions and 234 deletions
@@ -51,6 +51,19 @@ class ConstructorConverter(private val psiClass: PsiClass, private val converter
val parameterDefaults: List<PsiExpression>?) val parameterDefaults: List<PsiExpression>?)
private fun choosePrimaryConstructor(): PsiMethod? { private fun choosePrimaryConstructor(): PsiMethod? {
val toTargetConstructorMap = buildToTargetConstructorMap()
val candidates = constructors.filter { it !in toTargetConstructorMap }
if (candidates.size != 1) return null // there should be only one constructor which does not call other constructor
val primary = candidates.single()
if (toTargetConstructorMap.values().any() { it.constructor != primary }) return null // all other constructors call our candidate (directly or indirectly)
dropConstructorsForDefaultValues(primary, toTargetConstructorMap)
return primary
}
private fun buildToTargetConstructorMap(): Map<PsiMethod, TargetConstructorInfo> {
val toTargetConstructorMap = HashMap<PsiMethod, TargetConstructorInfo>() val toTargetConstructorMap = HashMap<PsiMethod, TargetConstructorInfo>()
for (constructor in constructors) { for (constructor in constructors) {
val firstStatement = constructor.getBody()?.getStatements()?.firstOrNull() val firstStatement = constructor.getBody()?.getStatements()?.firstOrNull()
@@ -85,15 +98,7 @@ class ConstructorConverter(private val psiClass: PsiClass, private val converter
} }
} }
} }
return toTargetConstructorMap
val candidates = constructors.filter { it !in toTargetConstructorMap }
if (candidates.size != 1) return null // there should be only one constructor which does not call other constructor
val primary = candidates.single()
if (toTargetConstructorMap.values().any() { it.constructor != primary }) return null // all other constructors call our candidate (directly or indirectly)
dropConstructorsForDefaultValues(primary, toTargetConstructorMap)
return primary
} }
private fun calcTargetParameterDefaults(constructor: PsiMethod, target: PsiMethod, targetCall: PsiMethodCallExpression): List<PsiExpression>? { private fun calcTargetParameterDefaults(constructor: PsiMethod, target: PsiMethod, targetCall: PsiMethodCallExpression): List<PsiExpression>? {
@@ -307,7 +312,7 @@ class ConstructorConverter(private val psiClass: PsiClass, private val converter
return generateArtificialPrimaryConstructor(classBody) return generateArtificialPrimaryConstructor(classBody)
} }
else { else {
val updatedFunctions = replaceConstructorCallsInFactoryFunctions(classBody.factoryFunctions) val updatedFunctions = processFactoryFunctionsWithConstructorCall(classBody.factoryFunctions)
return ClassBody(classBody.primaryConstructorSignature, classBody.members, classBody.classObjectMembers, updatedFunctions, classBody.lBrace, classBody.rBrace) return ClassBody(classBody.primaryConstructorSignature, classBody.members, classBody.classObjectMembers, updatedFunctions, classBody.lBrace, classBody.rBrace)
} }
} }
@@ -320,7 +325,7 @@ class ConstructorConverter(private val psiClass: PsiClass, private val converter
for (function in classBody.factoryFunctions) { for (function in classBody.factoryFunctions) {
val body = function.body!! val body = function.body!!
// 2 cases: secondary constructor either calls another constructor or does not call any // 2 cases: secondary constructor either calls another constructor or does not call any
val newStatements = replaceConstructorCallInFactoryFunction(body) ?: val newStatements = processFactoryFunctionWithConstructorCall(body) ?:
insertCallToArtificialPrimary(body, fieldsToInitialize) insertCallToArtificialPrimary(body, fieldsToInitialize)
val newBody = Block(newStatements, LBrace().assignNoPrototype(), RBrace().assignNoPrototype()).assignNoPrototype() val newBody = Block(newStatements, LBrace().assignNoPrototype(), RBrace().assignNoPrototype()).assignNoPrototype()
updatedFactoryFunctions.add(function.withBody(newBody)) updatedFactoryFunctions.add(function.withBody(newBody))
@@ -340,10 +345,10 @@ class ConstructorConverter(private val psiClass: PsiClass, private val converter
return ClassBody(constructorSignature, updatedMembers, classBody.classObjectMembers, updatedFactoryFunctions, classBody.lBrace, classBody.rBrace) return ClassBody(constructorSignature, updatedMembers, classBody.classObjectMembers, updatedFactoryFunctions, classBody.lBrace, classBody.rBrace)
} }
private fun replaceConstructorCallsInFactoryFunctions(functions: List<FactoryFunction>): List<FactoryFunction> { private fun processFactoryFunctionsWithConstructorCall(functions: List<FactoryFunction>): List<FactoryFunction> {
return functions.map { function -> return functions.map { function ->
val body = function.body!! val body = function.body!!
val statements = replaceConstructorCallInFactoryFunction(body) val statements = processFactoryFunctionWithConstructorCall(body)
if (statements != null) { if (statements != null) {
function.withBody(Block(statements, body.lBrace, body.rBrace).assignPrototypesFrom(body)) function.withBody(Block(statements, body.lBrace, body.rBrace).assignPrototypesFrom(body))
} }
@@ -353,7 +358,7 @@ class ConstructorConverter(private val psiClass: PsiClass, private val converter
} }
} }
private fun replaceConstructorCallInFactoryFunction(body: Block): List<Statement>? { private fun processFactoryFunctionWithConstructorCall(body: Block): List<Statement>? {
val statements = ArrayList(body.statements) val statements = ArrayList(body.statements)
// searching for other constructor call in form "this(...)" // searching for other constructor call in form "this(...)"
+7 -6
View File
@@ -149,10 +149,8 @@ public class Converter private(val project: Project, val settings: ConverterSett
else if (member is FactoryFunction) { else if (member is FactoryFunction) {
factoryFunctions.add(member) factoryFunctions.add(member)
} }
else if (useClassObject && else if (useClassObject
psiMember.hasModifierProperty(PsiModifier.STATIC) && && (if (member is Class) shouldGenerateIntoClassObject(member) else psiMember.hasModifierProperty(PsiModifier.STATIC))) {
// we generate nested classes with factory functions into class object as a workaround until secondary constructors supported by Kotlin
(psiMember !is PsiClass || (member as Class).body.factoryFunctions.isNotEmpty())) {
classObjectMembers.add(member) classObjectMembers.add(member)
} }
else { else {
@@ -169,8 +167,7 @@ public class Converter private(val project: Project, val settings: ConverterSett
private fun shouldGenerateClassObject(psiClass: PsiClass, convertedMembers: Map<PsiMember, Member>): Boolean { private fun shouldGenerateClassObject(psiClass: PsiClass, convertedMembers: Map<PsiMember, Member>): Boolean {
if (psiClass.isEnum()) return false if (psiClass.isEnum()) return false
// we generate nested classes with factory functions into class object as a workaround until secondary constructors supported by Kotlin if (convertedMembers.values().any { it is Class && shouldGenerateIntoClassObject(it) }) return true
if (convertedMembers.values().any { it is Class && !it.modifiers.contains(Modifier.INNER) && it.body.factoryFunctions.isNotEmpty() }) return true
val members = convertedMembers.keySet().filter { !it.isConstructor() } val members = convertedMembers.keySet().filter { !it.isConstructor() }
val classObjectMembers = members.filter { it !is PsiClass && it.hasModifierProperty(PsiModifier.STATIC) } val classObjectMembers = members.filter { it !is PsiClass && it.hasModifierProperty(PsiModifier.STATIC) }
@@ -183,6 +180,10 @@ public class Converter private(val project: Project, val settings: ConverterSett
} }
} }
// we generate nested classes with factory functions into class object as a workaround until secondary constructors supported by Kotlin
private fun shouldGenerateIntoClassObject(nestedClass: Class)
= !nestedClass.modifiers.contains(Modifier.INNER) && nestedClass.body.factoryFunctions.isNotEmpty()
private fun convertMember(member: PsiMember, membersToRemove: MutableSet<PsiMember>, constructorConverter: ConstructorConverter?): Member? = when (member) { private fun convertMember(member: PsiMember, membersToRemove: MutableSet<PsiMember>, constructorConverter: ConstructorConverter?): Member? = when (member) {
is PsiMethod -> convertMethod(member, membersToRemove, constructorConverter) is PsiMethod -> convertMethod(member, membersToRemove, constructorConverter)
is PsiField -> convertField(member) is PsiField -> convertField(member)
@@ -227,95 +227,23 @@ open class ExpressionVisitor(private val converter: Converter) : JavaElementVisi
} }
} }
if (target is PsiMethod && isObjectEquals(target) && arguments.size == 1) { if (target is PsiMethod) {
val qualifier = methodExpr.getQualifierExpression() val specialMethod = SpecialMethod.values().firstOrNull { it.matches(target) }
if (qualifier != null && qualifier !is PsiSuperExpression) { if (specialMethod != null && arguments.size == specialMethod.parameterCount) {
result = BinaryExpression(converter.convertExpression(qualifier), converter.convertExpression(arguments.single()), "==") val converted = specialMethod.convertCall(methodExpr.getQualifierExpression(), arguments, converter)
return if (converted != null) {
result = converted
return
}
} }
} }
if (target is PsiMethod && isObjectsEquals(target) && arguments.size == 2) {
result = BinaryExpression(converter.convertExpression(arguments[0]), converter.convertExpression(arguments[1]), "==")
return
}
//TODO: type arguments maybe required if we are in initializer of variable with no explicit type
if (target is PsiMethod && isCollectionsEmptyList(target) && arguments.size == 0) {
result = MethodCallExpression.build(null, "listOf", listOf(), listOf(), false)
return
}
//TODO: type arguments maybe required if we are in initializer of variable with no explicit type
if (target is PsiMethod && isCollectionsEmptySet(target) && arguments.size == 0) {
result = MethodCallExpression.build(null, "setOf", listOf(), listOf(), false)
return
}
//TODO: type arguments maybe required if we are in initializer of variable with no explicit type
if (target is PsiMethod && isCollectionsEmptyMap(target) && arguments.size == 0) {
result = MethodCallExpression.build(null, "mapOf", listOf(), listOf(), false)
return
}
if (target is PsiMethod && isCollectionsSingletonList(target) && arguments.size == 1) {
result = MethodCallExpression.build(null, "listOf", listOf(converter.convertExpression(arguments.single())), listOf(), false)
return
}
if (target is PsiMethod && isCollectionsSingleton(target) && arguments.size == 1) {
result = MethodCallExpression.build(null, "setOf", listOf(converter.convertExpression(arguments.single())), listOf(), false)
return
}
result = MethodCallExpression(converter.convertExpression(methodExpr), result = MethodCallExpression(converter.convertExpression(methodExpr),
convertArguments(expression), convertArguments(expression),
typeArguments, typeArguments,
isNullable) isNullable)
} }
private fun isObjectEquals(method: PsiMethod): Boolean {
return method.getName() == "equals" &&
method.getParameterList().getParameters().size == 1 &&
method.getParameterList().getParameters().single().getType().getCanonicalText() == JAVA_LANG_OBJECT
}
private fun isObjectsEquals(method: PsiMethod): Boolean {
return method.getName() == "equals" &&
method.getParameterList().getParameters().size == 2 &&
method.getContainingClass()?.getQualifiedName() == "java.util.Objects"
}
private fun isCollectionsEmptyList(method: PsiMethod): Boolean {
return method.getName() == "emptyList" &&
method.getParameterList().getParameters().size == 0 &&
method.getContainingClass()?.getQualifiedName() == "java.util.Collections"
}
private fun isCollectionsEmptySet(method: PsiMethod): Boolean {
return method.getName() == "emptySet" &&
method.getParameterList().getParameters().size == 0 &&
method.getContainingClass()?.getQualifiedName() == "java.util.Collections"
}
private fun isCollectionsEmptyMap(method: PsiMethod): Boolean {
return method.getName() == "emptyMap" &&
method.getParameterList().getParameters().size == 0 &&
method.getContainingClass()?.getQualifiedName() == "java.util.Collections"
}
private fun isCollectionsSingletonList(method: PsiMethod): Boolean {
return method.getName() == "singletonList" &&
method.getParameterList().getParameters().size == 1 &&
method.getContainingClass()?.getQualifiedName() == "java.util.Collections"
}
private fun isCollectionsSingleton(method: PsiMethod): Boolean {
return method.getName() == "singleton" &&
method.getParameterList().getParameters().size == 1 &&
method.getContainingClass()?.getQualifiedName() == "java.util.Collections"
}
override fun visitNewExpression(expression: PsiNewExpression) { override fun visitNewExpression(expression: PsiNewExpression) {
if (expression.getArrayInitializer() != null) { if (expression.getArrayInitializer() != null) {
result = converter.convertExpression(expression.getArrayInitializer()) result = converter.convertExpression(expression.getArrayInitializer())
@@ -0,0 +1,194 @@
/*
* Copyright 2010-2014 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.jet.j2k.visitors
import com.intellij.psi.PsiForStatement
import org.jetbrains.jet.j2k.ast.ForeachStatement
import com.intellij.psi.PsiDeclarationStatement
import org.jetbrains.jet.j2k.singleOrNull2
import com.intellij.psi.PsiLocalVariable
import org.jetbrains.jet.j2k.hasWriteAccesses
import com.intellij.psi.PsiBinaryExpression
import com.intellij.psi.JavaTokenType
import com.intellij.psi.PsiReferenceExpression
import com.intellij.psi.PsiExpressionStatement
import org.jetbrains.jet.j2k.ast.PrimitiveType
import org.jetbrains.jet.j2k.ast.Identifier
import org.jetbrains.jet.j2k.ast.assignNoPrototype
import org.jetbrains.jet.j2k.ast.declarationIdentifier
import org.jetbrains.jet.j2k.isInSingleLine
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiVariable
import com.intellij.psi.PsiPostfixExpression
import com.intellij.psi.PsiPrefixExpression
import com.intellij.psi.PsiExpression
import com.intellij.psi.tree.IElementType
import org.jetbrains.jet.j2k.ast.Expression
import com.intellij.psi.PsiLiteralExpression
import com.intellij.psi.PsiMethodCallExpression
import com.intellij.psi.PsiElementFactory
import com.intellij.psi.CommonClassNames
import org.jetbrains.jet.j2k.ast.QualifiedExpression
import com.intellij.psi.PsiArrayType
import org.jetbrains.jet.j2k.ast.BinaryExpression
import org.jetbrains.jet.j2k.ast.LiteralExpression
import org.jetbrains.jet.j2k.ast.RangeExpression
import org.jetbrains.jet.j2k.Converter
import com.intellij.psi.PsiBlockStatement
import com.intellij.psi.PsiNamedElement
import org.jetbrains.jet.j2k.ast.Block
import org.jetbrains.jet.j2k.ast.LBrace
import org.jetbrains.jet.j2k.ast.RBrace
import org.jetbrains.jet.j2k.ast.assignPrototypesFrom
import org.jetbrains.jet.j2k.ast.WhileStatement
import org.jetbrains.jet.j2k.ast.MethodCallExpression
import org.jetbrains.jet.j2k.ast.LambdaExpression
import org.jetbrains.jet.j2k.ast.Statement
class ForConverter(private val statement: PsiForStatement, private val converter: Converter) {
private val initialization = statement.getInitialization()
private val update = statement.getUpdate()
private val condition = statement.getCondition()
private val body = statement.getBody()
public fun execute(): Statement {
val foreach = convertToForeach()
if (foreach != null) return foreach
val initialization = statement.getInitialization()
val update = statement.getUpdate()
val condition = statement.getCondition()
val body = statement.getBody()
val initializationConverted = converter.convertStatement(initialization)
val updateConverted = converter.convertStatement(update)
val whileBody = if (updateConverted.isEmpty) {
converter.convertStatementOrBlock(body)
}
else if (body is PsiBlockStatement) {
val nameConflict = initialization is PsiDeclarationStatement && initialization.getDeclaredElements().any { loopVar ->
loopVar is PsiNamedElement && body.getCodeBlock().getStatements().any { statement ->
statement is PsiDeclarationStatement && statement.getDeclaredElements().any {
it is PsiNamedElement && it.getName() == loopVar.getName()
}
}
}
if (nameConflict) {
val statements = listOf(converter.convertStatement(body), updateConverted)
Block(statements, LBrace().assignNoPrototype(), RBrace().assignNoPrototype(), true).assignNoPrototype()
}
else {
val block = converter.convertBlock(body.getCodeBlock(), true)
Block(block.statements + listOf(updateConverted), block.lBrace, block.rBrace, true).assignPrototypesFrom(block)
}
}
else {
val statements = listOf(converter.convertStatement(body), updateConverted)
Block(statements, LBrace().assignNoPrototype(), RBrace().assignNoPrototype(), true).assignNoPrototype()
}
val whileStatement = WhileStatement(
if (condition != null) converter.convertExpression(condition) else LiteralExpression("true").assignNoPrototype(),
whileBody,
statement.isInSingleLine()).assignNoPrototype()
if (initializationConverted.isEmpty) return whileStatement
val statements = listOf(initializationConverted, whileStatement)
val block = Block(statements, LBrace().assignNoPrototype(), RBrace().assignNoPrototype()).assignNoPrototype()
return MethodCallExpression.build(null, "run", listOf(), listOf(), false, LambdaExpression(null, block))
}
private fun convertToForeach(): ForeachStatement? {
if (initialization is PsiDeclarationStatement) {
val loopVar = initialization.getDeclaredElements().singleOrNull2() as? PsiLocalVariable
if (loopVar != null
&& !loopVar.hasWriteAccesses(body)
&& !loopVar.hasWriteAccesses(condition)
&& condition is PsiBinaryExpression) {
val operationTokenType = condition.getOperationTokenType()
val lowerBound = condition.getLOperand()
val upperBound = condition.getROperand()
if ((operationTokenType == JavaTokenType.LT || operationTokenType == JavaTokenType.LE) &&
lowerBound is PsiReferenceExpression &&
lowerBound.resolve() == loopVar &&
upperBound != null) {
val start = loopVar.getInitializer()
if (start != null &&
(update as? PsiExpressionStatement)?.getExpression()?.isVariablePlusPlus(loopVar) ?: false) {
val range = forIterationRange(start, upperBound, operationTokenType).assignNoPrototype()
val explicitType = if (converter.settings.specifyLocalVariableTypeByDefault)
PrimitiveType(Identifier("Int").assignNoPrototype()).assignNoPrototype()
else
null
return ForeachStatement(loopVar.declarationIdentifier(), explicitType, range, converter.convertStatementOrBlock(body), statement.isInSingleLine())
}
}
}
}
return null
}
private fun PsiElement.isVariablePlusPlus(variable: PsiVariable): Boolean {
//TODO: simplify code when KT-5453 fixed
val pair = when (this) {
is PsiPostfixExpression -> getOperationTokenType() to getOperand()
is PsiPrefixExpression -> getOperationTokenType() to getOperand()
else -> return false
}
return pair.first == JavaTokenType.PLUSPLUS && (pair.second as? PsiReferenceExpression)?.resolve() == variable
}
private fun forIterationRange(start: PsiExpression, upperBound: PsiExpression, comparisonTokenType: IElementType): Expression {
if (start is PsiLiteralExpression
&& start.getValue() == 0
&& comparisonTokenType == JavaTokenType.LT) {
// check if it's iteration through list indices
if (upperBound is PsiMethodCallExpression && upperBound.getArgumentList().getExpressions().isEmpty()) {
val methodExpr = upperBound.getMethodExpression()
if (methodExpr is PsiReferenceExpression && methodExpr.getReferenceName() == "size") {
val qualifier = methodExpr.getQualifierExpression()
if (qualifier is PsiReferenceExpression /* we don't convert to .indices if qualifier is method call or something because of possible side effects */) {
val listType = PsiElementFactory.SERVICE.getInstance(converter.project).createTypeByFQClassName(CommonClassNames.JAVA_UTIL_LIST)
val qualifierType = qualifier.getType()
if (qualifierType != null && listType.isAssignableFrom(qualifierType)) {
return QualifiedExpression(converter.convertExpression(qualifier), Identifier("indices", false).assignNoPrototype())
}
}
}
}
// check if it's iteration through array indices
if (upperBound is PsiReferenceExpression /* we don't convert to .indices if qualifier is method call or something because of possible side effects */
&& upperBound.getReferenceName() == "length") {
val qualifier = upperBound.getQualifierExpression()
if (qualifier is PsiReferenceExpression && qualifier.getType() is PsiArrayType) {
return QualifiedExpression(converter.convertExpression(qualifier), Identifier("indices", false).assignNoPrototype())
}
}
}
val end = converter.convertExpression(upperBound)
val endExpression = if (comparisonTokenType == JavaTokenType.LT)
BinaryExpression(end, LiteralExpression("1").assignNoPrototype(), "-").assignNoPrototype()
else
end
return RangeExpression(converter.convertExpression(start), endExpression)
}
}
@@ -0,0 +1,79 @@
/*
* Copyright 2010-2014 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.jet.j2k.visitors
import com.intellij.psi.PsiMethod
import com.intellij.psi.PsiExpression
import org.jetbrains.jet.j2k.Converter
import org.jetbrains.jet.j2k.ast.BinaryExpression
import org.jetbrains.jet.j2k.ast.Expression
import org.jetbrains.jet.j2k.ast.MethodCallExpression
import com.intellij.psi.CommonClassNames.JAVA_LANG_OBJECT
import com.intellij.psi.PsiSuperExpression
enum class SpecialMethod(val qualifiedClassName: String?, val methodName: String, val parameterCount: Int) {
OBJECT_EQUALS: SpecialMethod(null, "equals", 1) {
override fun matches(method: PsiMethod)
= super.matches(method) && method.getParameterList().getParameters().single().getType().getCanonicalText() == JAVA_LANG_OBJECT
override fun convertCall(qualifier: PsiExpression?, arguments: Array<PsiExpression>, converter: Converter): Expression? {
if (qualifier == null || qualifier is PsiSuperExpression) return null
return BinaryExpression(converter.convertExpression(qualifier), converter.convertExpression(arguments.single()), "==")
}
}
OBJECTS_EQUALS: SpecialMethod("java.util.Objects", "equals", 2) {
override fun convertCall(qualifier: PsiExpression?, arguments: Array<PsiExpression>, converter: Converter)
= BinaryExpression(converter.convertExpression(arguments[0]), converter.convertExpression(arguments[1]), "==")
}
//TODO: type arguments maybe required if we are in initializer of variable with no explicit type
COLLECTIONS_EMPTY_LIST: SpecialMethod("java.util.Collections", "emptyList", 0) {
override fun convertCall(qualifier: PsiExpression?, arguments: Array<PsiExpression>, converter: Converter)
= MethodCallExpression.build(null, "listOf", listOf(), listOf(), false)
}
//TODO: type arguments maybe required if we are in initializer of variable with no explicit type
COLLECTIONS_EMPTY_SET: SpecialMethod("java.util.Collections", "emptySet", 0) {
override fun convertCall(qualifier: PsiExpression?, arguments: Array<PsiExpression>, converter: Converter)
= MethodCallExpression.build(null, "setOf", listOf(), listOf(), false)
}
//TODO: type arguments maybe required if we are in initializer of variable with no explicit type
COLLECTIONS_EMPTY_MAP: SpecialMethod("java.util.Collections", "emptyMap", 0) {
override fun convertCall(qualifier: PsiExpression?, arguments: Array<PsiExpression>, converter: Converter)
= MethodCallExpression.build(null, "mapOf", listOf(), listOf(), false)
}
COLLECTIONS_SINGLETON_LIST: SpecialMethod("java.util.Collections", "singletonList", 1) {
override fun convertCall(qualifier: PsiExpression?, arguments: Array<PsiExpression>, converter: Converter)
= MethodCallExpression.build(null, "listOf", listOf(converter.convertExpression(arguments.single())), listOf(), false)
}
COLLECTIONS_SINGLETON: SpecialMethod("java.util.Collections", "singleton", 1) {
override fun convertCall(qualifier: PsiExpression?, arguments: Array<PsiExpression>, converter: Converter)
= MethodCallExpression.build(null, "setOf", listOf(converter.convertExpression(arguments.single())), listOf(), false)
}
open fun matches(method: PsiMethod): Boolean {
if (method.getName() != methodName) return false
if (qualifiedClassName != null && method.getContainingClass()?.getQualifiedName() != qualifiedClassName) return false
return method.getParameterList().getParametersCount() == parameterCount
}
abstract fun convertCall(qualifier: PsiExpression?, arguments: Array<PsiExpression>, converter: Converter): Expression?
}
@@ -19,12 +19,8 @@ package org.jetbrains.jet.j2k.visitors
import com.intellij.psi.* import com.intellij.psi.*
import org.jetbrains.jet.j2k.Converter import org.jetbrains.jet.j2k.Converter
import org.jetbrains.jet.j2k.ast.* import org.jetbrains.jet.j2k.ast.*
import org.jetbrains.jet.j2k.countWriteAccesses
import java.util.ArrayList import java.util.ArrayList
import org.jetbrains.jet.j2k.hasWriteAccesses
import org.jetbrains.jet.j2k.isInSingleLine import org.jetbrains.jet.j2k.isInSingleLine
import com.intellij.psi.tree.IElementType
import org.jetbrains.jet.j2k.singleOrNull2
open class StatementVisitor(public val converter: Converter) : JavaElementVisitor() { open class StatementVisitor(public val converter: Converter) : JavaElementVisitor() {
public var result: Statement = Statement.Empty public var result: Statement = Statement.Empty
@@ -86,7 +82,7 @@ open class StatementVisitor(public val converter: Converter) : JavaElementVisito
converter.convertExpression(condition, condition.getType()) converter.convertExpression(condition, condition.getType())
else else
converter.convertExpression(condition) converter.convertExpression(condition)
result = DoWhileStatement(expression, convertStatementOrBlock(statement.getBody()), statement.isInSingleLine()) result = DoWhileStatement(expression, converter.convertStatementOrBlock(statement.getBody()), statement.isInSingleLine())
} }
override fun visitExpressionStatement(statement: PsiExpressionStatement) { override fun visitExpressionStatement(statement: PsiExpressionStatement) {
@@ -98,124 +94,7 @@ open class StatementVisitor(public val converter: Converter) : JavaElementVisito
} }
override fun visitForStatement(statement: PsiForStatement) { override fun visitForStatement(statement: PsiForStatement) {
val initialization = statement.getInitialization() result = ForConverter(statement, converter).execute()
val update = statement.getUpdate()
val condition = statement.getCondition()
val body = statement.getBody()
if (initialization is PsiDeclarationStatement) {
val loopVar = initialization.getDeclaredElements().singleOrNull2() as? PsiLocalVariable
if (loopVar != null
&& !loopVar.hasWriteAccesses(body)
&& !loopVar.hasWriteAccesses(condition)
&& condition is PsiBinaryExpression) {
val operationTokenType = condition.getOperationTokenType()
val lowerBound = condition.getLOperand()
val upperBound = condition.getROperand()
if ((operationTokenType == JavaTokenType.LT || operationTokenType == JavaTokenType.LE) &&
lowerBound is PsiReferenceExpression &&
lowerBound.resolve() == loopVar &&
upperBound != null) {
val start = loopVar.getInitializer()
if (start != null &&
(update as? PsiExpressionStatement)?.getExpression()?.isVariablePlusPlus(loopVar) ?: false) {
val range = forIterationRange(start, upperBound, operationTokenType).assignNoPrototype()
val explicitType = if (converter.settings.specifyLocalVariableTypeByDefault) PrimitiveType(Identifier("Int").assignNoPrototype()).assignNoPrototype() else null
result = ForeachStatement(loopVar.declarationIdentifier(), explicitType, range, convertStatementOrBlock(body), statement.isInSingleLine())
return
}
}
}
}
val initializationConverted = converter.convertStatement(initialization)
val updateConverted = converter.convertStatement(update)
val whileBody = if (updateConverted.isEmpty) {
convertStatementOrBlock(body)
}
else if (body is PsiBlockStatement) {
val nameConflict = initialization is PsiDeclarationStatement && initialization.getDeclaredElements().any { loopVar ->
loopVar is PsiNamedElement && body.getCodeBlock().getStatements().any { statement ->
statement is PsiDeclarationStatement && statement.getDeclaredElements().any {
it is PsiNamedElement && it.getName() == loopVar.getName()
}
}
}
if (nameConflict) {
val statements = listOf(converter.convertStatement(body), updateConverted)
Block(statements, LBrace().assignNoPrototype(), RBrace().assignNoPrototype(), true).assignNoPrototype()
}
else {
val block = converter.convertBlock(body.getCodeBlock(), true)
Block(block.statements + listOf(updateConverted), block.lBrace, block.rBrace, true).assignPrototypesFrom(block)
}
}
else {
val statements = listOf(converter.convertStatement(body), updateConverted)
Block(statements, LBrace().assignNoPrototype(), RBrace().assignNoPrototype(), true).assignNoPrototype()
}
val whileStatement = WhileStatement(
if (condition != null) converter.convertExpression(condition) else LiteralExpression("true").assignNoPrototype(),
whileBody,
statement.isInSingleLine()).assignNoPrototype()
if (initializationConverted.isEmpty) {
result = whileStatement
}
else {
val statements = listOf(initializationConverted, whileStatement)
val block = Block(statements, LBrace().assignNoPrototype(), RBrace().assignNoPrototype()).assignNoPrototype()
result = MethodCallExpression.build(null, "run", listOf(), listOf(), false, LambdaExpression(null, block))
}
}
private fun PsiElement.isVariablePlusPlus(variable: PsiVariable): Boolean {
//TODO: simplify code when KT-5453 fixed
val pair = when (this) {
is PsiPostfixExpression -> getOperationTokenType() to getOperand()
is PsiPrefixExpression -> getOperationTokenType() to getOperand()
else -> return false
}
return pair.first == JavaTokenType.PLUSPLUS && (pair.second as? PsiReferenceExpression)?.resolve() == variable
}
private fun forIterationRange(start: PsiExpression, upperBound: PsiExpression, comparisonTokenType: IElementType): Expression {
if (start is PsiLiteralExpression
&& start.getValue() == 0
&& comparisonTokenType == JavaTokenType.LT) {
// check if it's iteration through list indices
if (upperBound is PsiMethodCallExpression && upperBound.getArgumentList().getExpressions().isEmpty()) {
val methodExpr = upperBound.getMethodExpression()
if (methodExpr is PsiReferenceExpression && methodExpr.getReferenceName() == "size") {
val qualifier = methodExpr.getQualifierExpression()
if (qualifier is PsiReferenceExpression /* we don't convert to .indices if qualifier is method call or something because of possible side effects */) {
val listType = PsiElementFactory.SERVICE.getInstance(converter.project).createTypeByFQClassName(CommonClassNames.JAVA_UTIL_LIST)
val qualifierType = qualifier.getType()
if (qualifierType != null && listType.isAssignableFrom(qualifierType)) {
return QualifiedExpression(converter.convertExpression(qualifier), Identifier("indices", false).assignNoPrototype())
}
}
}
}
// check if it's iteration through array indices
if (upperBound is PsiReferenceExpression /* we don't convert to .indices if qualifier is method call or something because of possible side effects */
&& upperBound.getReferenceName() == "length") {
val qualifier = upperBound.getQualifierExpression()
if (qualifier is PsiReferenceExpression && qualifier.getType() is PsiArrayType) {
return QualifiedExpression(converter.convertExpression(qualifier), Identifier("indices", false).assignNoPrototype())
}
}
}
val end = converter.convertExpression(upperBound)
val endExpression = if (comparisonTokenType == JavaTokenType.LT)
BinaryExpression(end, LiteralExpression("1").assignNoPrototype(), "-").assignNoPrototype()
else
end
return RangeExpression(converter.convertExpression(start), endExpression)
} }
override fun visitForeachStatement(statement: PsiForeachStatement) { override fun visitForeachStatement(statement: PsiForeachStatement) {
@@ -225,7 +104,7 @@ open class StatementVisitor(public val converter: Converter) : JavaElementVisito
result = ForeachStatement(iterationParameter.declarationIdentifier(), result = ForeachStatement(iterationParameter.declarationIdentifier(),
if (converter.settings.specifyLocalVariableTypeByDefault) converter.typeConverter.convertVariableType(iterationParameter) else null, if (converter.settings.specifyLocalVariableTypeByDefault) converter.typeConverter.convertVariableType(iterationParameter) else null,
iterator, iterator,
convertStatementOrBlock(statement.getBody()), converter.convertStatementOrBlock(statement.getBody()),
statement.isInSingleLine()) statement.isInSingleLine())
} }
@@ -233,8 +112,8 @@ open class StatementVisitor(public val converter: Converter) : JavaElementVisito
val condition = statement.getCondition() val condition = statement.getCondition()
val expression = converter.convertExpression(condition, PsiType.BOOLEAN) val expression = converter.convertExpression(condition, PsiType.BOOLEAN)
result = IfStatement(expression, result = IfStatement(expression,
convertStatementOrBlock(statement.getThenBranch()), converter.convertStatementOrBlock(statement.getThenBranch()),
convertStatementOrBlock(statement.getElseBranch()), converter.convertStatementOrBlock(statement.getElseBranch()),
statement.isInSingleLine()) statement.isInSingleLine())
} }
@@ -328,7 +207,7 @@ open class StatementVisitor(public val converter: Converter) : JavaElementVisito
converter.convertExpression(condition, condition!!.getType()) converter.convertExpression(condition, condition!!.getType())
else else
converter.convertExpression(condition) converter.convertExpression(condition)
result = WhileStatement(expression, convertStatementOrBlock(statement.getBody()), statement.isInSingleLine()) result = WhileStatement(expression, converter.convertStatementOrBlock(statement.getBody()), statement.isInSingleLine())
} }
override fun visitReturnStatement(statement: PsiReturnStatement) { override fun visitReturnStatement(statement: PsiReturnStatement) {
@@ -344,11 +223,12 @@ open class StatementVisitor(public val converter: Converter) : JavaElementVisito
override fun visitEmptyStatement(statement: PsiEmptyStatement) { override fun visitEmptyStatement(statement: PsiEmptyStatement) {
result = Statement.Empty result = Statement.Empty
} }
private fun convertStatementOrBlock(statement: PsiStatement?): Statement {
return if (statement is PsiBlockStatement)
converter.convertBlock(statement.getCodeBlock())
else
converter.convertStatement(statement)
}
} }
fun Converter.convertStatementOrBlock(statement: PsiStatement?): Statement {
return if (statement is PsiBlockStatement)
convertBlock(statement.getCodeBlock())
else
convertStatement(statement)
}
@@ -811,6 +811,11 @@ public class JavaToKotlinConverterTestGenerated extends AbstractJavaToKotlinConv
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.TestsPackage", new File("j2k/tests/testData/ast/constructors"), Pattern.compile("^(.+)\\.java$"), true); JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.TestsPackage", new File("j2k/tests/testData/ast/constructors"), Pattern.compile("^(.+)\\.java$"), true);
} }
@TestMetadata("artificialPrimary.java")
public void testArtificialPrimary() throws Exception {
doTest("j2k/tests/testData/ast/constructors/artificialPrimary.java");
}
@TestMetadata("chain.java") @TestMetadata("chain.java")
public void testChain() throws Exception { public void testChain() throws Exception {
doTest("j2k/tests/testData/ast/constructors/chain.java"); doTest("j2k/tests/testData/ast/constructors/chain.java");
@@ -0,0 +1,14 @@
//file
public class Test {
private final String s;
boolean b;
double d;
public Test() {
b = true;
}
public Test(String s) {
this.s = s;
}
}
@@ -0,0 +1,14 @@
public fun Test(): Test {
val __ = Test(null)
__.b = true
return __
}
public fun Test(s: String): Test {
return Test(s)
}
public class Test(private val s: String) {
var b: Boolean = false
var d: Double = 0.toDouble()
}