Java to Kotlin converter: code refactorings after review
This commit is contained in:
@@ -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(...)"
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
Reference in New Issue
Block a user