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>?)
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>()
for (constructor in constructors) {
val firstStatement = constructor.getBody()?.getStatements()?.firstOrNull()
@@ -85,15 +98,7 @@ class ConstructorConverter(private val psiClass: PsiClass, private val converter
}
}
}
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
return toTargetConstructorMap
}
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)
}
else {
val updatedFunctions = replaceConstructorCallsInFactoryFunctions(classBody.factoryFunctions)
val updatedFunctions = processFactoryFunctionsWithConstructorCall(classBody.factoryFunctions)
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) {
val body = function.body!!
// 2 cases: secondary constructor either calls another constructor or does not call any
val newStatements = replaceConstructorCallInFactoryFunction(body) ?:
val newStatements = processFactoryFunctionWithConstructorCall(body) ?:
insertCallToArtificialPrimary(body, fieldsToInitialize)
val newBody = Block(newStatements, LBrace().assignNoPrototype(), RBrace().assignNoPrototype()).assignNoPrototype()
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)
}
private fun replaceConstructorCallsInFactoryFunctions(functions: List<FactoryFunction>): List<FactoryFunction> {
private fun processFactoryFunctionsWithConstructorCall(functions: List<FactoryFunction>): List<FactoryFunction> {
return functions.map { function ->
val body = function.body!!
val statements = replaceConstructorCallInFactoryFunction(body)
val statements = processFactoryFunctionWithConstructorCall(body)
if (statements != null) {
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)
// 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) {
factoryFunctions.add(member)
}
else if (useClassObject &&
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())) {
else if (useClassObject
&& (if (member is Class) shouldGenerateIntoClassObject(member) else psiMember.hasModifierProperty(PsiModifier.STATIC))) {
classObjectMembers.add(member)
}
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 {
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 && !it.modifiers.contains(Modifier.INNER) && it.body.factoryFunctions.isNotEmpty() }) return true
if (convertedMembers.values().any { it is Class && shouldGenerateIntoClassObject(it) }) return true
val members = convertedMembers.keySet().filter { !it.isConstructor() }
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) {
is PsiMethod -> convertMethod(member, membersToRemove, constructorConverter)
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) {
val qualifier = methodExpr.getQualifierExpression()
if (qualifier != null && qualifier !is PsiSuperExpression) {
result = BinaryExpression(converter.convertExpression(qualifier), converter.convertExpression(arguments.single()), "==")
return
if (target is PsiMethod) {
val specialMethod = SpecialMethod.values().firstOrNull { it.matches(target) }
if (specialMethod != null && arguments.size == specialMethod.parameterCount) {
val converted = specialMethod.convertCall(methodExpr.getQualifierExpression(), arguments, converter)
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),
convertArguments(expression),
typeArguments,
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) {
if (expression.getArrayInitializer() != null) {
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 org.jetbrains.jet.j2k.Converter
import org.jetbrains.jet.j2k.ast.*
import org.jetbrains.jet.j2k.countWriteAccesses
import java.util.ArrayList
import org.jetbrains.jet.j2k.hasWriteAccesses
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() {
public var result: Statement = Statement.Empty
@@ -86,7 +82,7 @@ open class StatementVisitor(public val converter: Converter) : JavaElementVisito
converter.convertExpression(condition, condition.getType())
else
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) {
@@ -98,124 +94,7 @@ open class StatementVisitor(public val converter: Converter) : JavaElementVisito
}
override fun visitForStatement(statement: PsiForStatement) {
val initialization = statement.getInitialization()
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)
result = ForConverter(statement, converter).execute()
}
override fun visitForeachStatement(statement: PsiForeachStatement) {
@@ -225,7 +104,7 @@ open class StatementVisitor(public val converter: Converter) : JavaElementVisito
result = ForeachStatement(iterationParameter.declarationIdentifier(),
if (converter.settings.specifyLocalVariableTypeByDefault) converter.typeConverter.convertVariableType(iterationParameter) else null,
iterator,
convertStatementOrBlock(statement.getBody()),
converter.convertStatementOrBlock(statement.getBody()),
statement.isInSingleLine())
}
@@ -233,8 +112,8 @@ open class StatementVisitor(public val converter: Converter) : JavaElementVisito
val condition = statement.getCondition()
val expression = converter.convertExpression(condition, PsiType.BOOLEAN)
result = IfStatement(expression,
convertStatementOrBlock(statement.getThenBranch()),
convertStatementOrBlock(statement.getElseBranch()),
converter.convertStatementOrBlock(statement.getThenBranch()),
converter.convertStatementOrBlock(statement.getElseBranch()),
statement.isInSingleLine())
}
@@ -328,7 +207,7 @@ open class StatementVisitor(public val converter: Converter) : JavaElementVisito
converter.convertExpression(condition, condition!!.getType())
else
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) {
@@ -344,11 +223,12 @@ open class StatementVisitor(public val converter: Converter) : JavaElementVisito
override fun visitEmptyStatement(statement: PsiEmptyStatement) {
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);
}
@TestMetadata("artificialPrimary.java")
public void testArtificialPrimary() throws Exception {
doTest("j2k/tests/testData/ast/constructors/artificialPrimary.java");
}
@TestMetadata("chain.java")
public void testChain() throws Exception {
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()
}