From 8c5c1fee2cf77a4cb9b50fcf1b306435096b00eb Mon Sep 17 00:00:00 2001 From: Alexey Belkov Date: Tue, 23 May 2023 13:25:37 +0400 Subject: [PATCH] [kotlin] J2K: refactor BuiltinMembersConversion Split conversions into groups, move nested declarations to the top level. GitOrigin-RevId: 631ef72dcabb5425a9f5a01071f54f40868f7fe6 --- .../conversions/BuiltinMembersConversion.kt | 1154 +++++++++-------- 1 file changed, 596 insertions(+), 558 deletions(-) diff --git a/plugins/kotlin/j2k/new/src/org/jetbrains/kotlin/nj2k/conversions/BuiltinMembersConversion.kt b/plugins/kotlin/j2k/new/src/org/jetbrains/kotlin/nj2k/conversions/BuiltinMembersConversion.kt index 0499afd5cd73..874a802a76ed 100644 --- a/plugins/kotlin/j2k/new/src/org/jetbrains/kotlin/nj2k/conversions/BuiltinMembersConversion.kt +++ b/plugins/kotlin/j2k/new/src/org/jetbrains/kotlin/nj2k/conversions/BuiltinMembersConversion.kt @@ -5,19 +5,21 @@ package org.jetbrains.kotlin.nj2k.conversions import org.jetbrains.kotlin.builtins.StandardNames import org.jetbrains.kotlin.config.ApiVersion import org.jetbrains.kotlin.nj2k.* +import org.jetbrains.kotlin.nj2k.conversions.ReplaceType.REPLACE_SELECTOR +import org.jetbrains.kotlin.nj2k.conversions.ReplaceType.REPLACE_WITH_QUALIFIER import org.jetbrains.kotlin.nj2k.symbols.JKMethodSymbol import org.jetbrains.kotlin.nj2k.symbols.JKUnresolvedField import org.jetbrains.kotlin.nj2k.symbols.deepestFqName import org.jetbrains.kotlin.nj2k.tree.* import org.jetbrains.kotlin.nj2k.tree.JKLiteralExpression.LiteralType.STRING -import org.jetbrains.kotlin.nj2k.types.JKClassType -import org.jetbrains.kotlin.nj2k.types.isArrayType -import org.jetbrains.kotlin.nj2k.types.isNull -import org.jetbrains.kotlin.nj2k.types.isStringType +import org.jetbrains.kotlin.nj2k.types.* import org.jetbrains.kotlin.utils.addToStdlib.cast import org.jetbrains.kotlin.utils.addToStdlib.safeAs class BuiltinMembersConversion(context: NewJ2kConverterContext) : RecursiveApplicableConversionBase(context) { + private val conversions: Map> = + ConversionsHolder(symbolProvider, typeFactory).getConversions() + override fun applyToElement(element: JKTreeElement): JKTreeElement { if (element !is JKExpression) return recurse(element) return recurse(element.convert() ?: element) @@ -32,7 +34,7 @@ class BuiltinMembersConversion(context: NewJ2kConverterContext) : RecursiveAppli val conversion = selector.getConversion() ?: return null val newSelector = conversion.createBuilder().build(selector) - if (this is JKQualifiedExpression && conversion.replaceType == ReplaceType.REPLACE_WITH_QUALIFIER) { + if (this is JKQualifiedExpression && conversion.replaceType == REPLACE_WITH_QUALIFIER) { newSelector.leadingComments += receiver.trailingComments newSelector.leadingComments += receiver.leadingComments newSelector.leadingComments += selector.trailingComments @@ -40,14 +42,14 @@ class BuiltinMembersConversion(context: NewJ2kConverterContext) : RecursiveAppli } return when (conversion.replaceType) { - ReplaceType.REPLACE_SELECTOR -> { + REPLACE_SELECTOR -> { if (this is JKQualifiedExpression) { this.selector = newSelector this } else newSelector } - ReplaceType.REPLACE_WITH_QUALIFIER -> newSelector + REPLACE_WITH_QUALIFIER -> newSelector }.let { expression -> conversion.actionAfter?.invoke(expression.copyTreeAndDetach()) ?: expression } @@ -90,7 +92,6 @@ class BuiltinMembersConversion(context: NewJ2kConverterContext) : RecursiveAppli conversion.sinceKotlin?.let { it <= moduleApiVersion } ?: true } - private interface ResultBuilder { fun build(from: JKExpression): JKExpression } @@ -135,9 +136,7 @@ class BuiltinMembersConversion(context: NewJ2kConverterContext) : RecursiveAppli } } - private inner class FieldBuilder( - private val fqName: String - ) : ResultBuilder { + private inner class FieldBuilder(private val fqName: String) : ResultBuilder { override fun build(from: JKExpression): JKExpression = when (from) { is JKCallExpression -> @@ -154,9 +153,7 @@ class BuiltinMembersConversion(context: NewJ2kConverterContext) : RecursiveAppli } } - private inner class ExtensionMethodBuilder( - private val fqName: String - ) : ResultBuilder { + private inner class ExtensionMethodBuilder(private val fqName: String) : ResultBuilder { override fun build(from: JKExpression): JKExpression = when (from) { is JKCallExpression -> { @@ -175,576 +172,606 @@ class BuiltinMembersConversion(context: NewJ2kConverterContext) : RecursiveAppli } } - private inner class CustomExpressionBuilder( - val builder: (JKExpression) -> JKExpression - ) : ResultBuilder { + private inner class CustomExpressionBuilder(val builder: (JKExpression) -> JKExpression) : ResultBuilder { override fun build(from: JKExpression): JKExpression = builder(from) } - private fun Conversion.createBuilder(): ResultBuilder = - when (to) { - is Method -> MethodBuilder(to.fqName, to.parameterTypesFqNames, argumentsProvider ?: { it }) - is Field -> FieldBuilder(to.fqName) - is ExtensionMethod -> ExtensionMethodBuilder(to.fqName) - is CustomExpression -> CustomExpressionBuilder(to.expressionBuilder) - else -> error("Bad conversion") - } - - - private enum class ReplaceType { - REPLACE_SELECTOR, REPLACE_WITH_QUALIFIER + private fun Conversion.createBuilder(): ResultBuilder = when (to) { + is Method -> MethodBuilder(to.fqName, to.parameterTypesFqNames, argumentsProvider ?: { it }) + is Field -> FieldBuilder(to.fqName) + is ExtensionMethod -> ExtensionMethodBuilder(to.fqName) + is CustomExpression -> CustomExpressionBuilder(to.expressionBuilder) + else -> error("Bad conversion") } +} +private enum class ReplaceType { REPLACE_SELECTOR, REPLACE_WITH_QUALIFIER } - private interface Info - private interface SymbolInfo : Info { - val fqName: String - } - - private data class Method(override val fqName: String, val parameterTypesFqNames: List? = null) : SymbolInfo - private data class NewExpression(override val fqName: String) : SymbolInfo - private data class Field(override val fqName: String) : SymbolInfo - private data class ExtensionMethod(override val fqName: String) : SymbolInfo - private data class CustomExpression(val expressionBuilder: (JKExpression) -> JKExpression) : Info - - private data class Conversion( - val from: SymbolInfo, - val to: Info, - val sinceKotlin: ApiVersion? = null, - val replaceType: ReplaceType = ReplaceType.REPLACE_SELECTOR, - val filter: ((JKExpression) -> Boolean)? = null, - val byArgumentsFilter: ((List) -> Boolean)? = null, - val argumentsProvider: ((JKArgumentList) -> JKArgumentList)? = null, - val actionAfter: ((JKExpression) -> JKExpression)? = null - ) - - private infix fun SymbolInfo.convertTo(to: Info) = - Conversion(this, to) - - private infix fun Conversion.sinceKotlin(apiVersion: ApiVersion) = +private data class Conversion( + val from: SymbolInfo, + val to: Info, + val sinceKotlin: ApiVersion? = null, + val replaceType: ReplaceType = REPLACE_SELECTOR, + val filter: ((JKExpression) -> Boolean)? = null, + val byArgumentsFilter: ((List) -> Boolean)? = null, + val argumentsProvider: ((JKArgumentList) -> JKArgumentList)? = null, + val actionAfter: ((JKExpression) -> JKExpression)? = null +) { + infix fun sinceKotlin(apiVersion: ApiVersion): Conversion = copy(sinceKotlin = apiVersion) - private infix fun Conversion.withReplaceType(replaceType: ReplaceType) = + infix fun withReplaceType(replaceType: ReplaceType): Conversion = copy(replaceType = replaceType) - private infix fun Conversion.withFilter(filter: (JKExpression) -> Boolean) = + infix fun withFilter(filter: (JKExpression) -> Boolean): Conversion = copy(filter = filter) - private infix fun Conversion.withByArgumentsFilter(filter: (List) -> Boolean) = + infix fun withByArgumentsFilter(filter: (List) -> Boolean): Conversion = copy(byArgumentsFilter = filter) - private infix fun Conversion.withArgumentsProvider(argumentsProvider: (JKArgumentList) -> JKArgumentList) = + infix fun withArgumentsProvider(argumentsProvider: (JKArgumentList) -> JKArgumentList): Conversion = copy(argumentsProvider = argumentsProvider) - private infix fun Conversion.andAfter(actionAfter: (JKExpression) -> JKExpression) = + infix fun andAfter(actionAfter: (JKExpression) -> JKExpression): Conversion = copy(actionAfter = actionAfter) +} - private val conversions: Map> = - listOf( - Method("java.lang.Boolean.valueOf") convertTo valueOfReplacement() - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER, - Method("java.lang.Byte.valueOf") convertTo valueOfReplacement() - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER, - Method("java.lang.Short.valueOf") convertTo valueOfReplacement() - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER, - Method("java.lang.Integer.valueOf") convertTo valueOfReplacement() - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER, - Method("java.lang.Long.valueOf") convertTo valueOfReplacement() - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER, - Method("java.lang.Float.valueOf") convertTo valueOfReplacement() - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER, - Method("java.lang.Double.valueOf") convertTo valueOfReplacement() - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER, +private interface Info - Method("java.lang.Boolean.parseBoolean") convertTo ExtensionMethod("kotlin.text.toBoolean") - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER, - Method("java.lang.Byte.parseByte") convertTo ExtensionMethod("kotlin.text.toByte") - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER, - Method("java.lang.Short.parseShort") convertTo ExtensionMethod("kotlin.text.toShort") - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER, - Method("java.lang.Integer.parseInt") convertTo ExtensionMethod("kotlin.text.toInt") - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER - withByArgumentsFilter { it.size <= 2 }, - Method("java.lang.Long.parseLong") convertTo ExtensionMethod("kotlin.text.toLong") - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER - withByArgumentsFilter { it.size <= 2 }, - Method("java.lang.Float.parseFloat") convertTo ExtensionMethod("kotlin.text.toFloat") - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER, - Method("java.lang.Double.parseDouble") convertTo ExtensionMethod("kotlin.text.toDouble") - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER, +private interface SymbolInfo : Info { + val fqName: String - Method("java.lang.Number.byteValue") convertTo Method("kotlin.Number.toByte"), - Method("java.lang.Number.doubleValue") convertTo Method("kotlin.Number.toDouble"), - Method("java.lang.Number.floatValue") convertTo Method("kotlin.Number.toFloat"), - Method("java.lang.Number.intValue") convertTo Method("kotlin.Number.toInt"), - Method("java.lang.Number.longValue") convertTo Method("kotlin.Number.toLong"), - Method("java.lang.Number.shortValue") convertTo Method("kotlin.Number.toShort"), + infix fun convertTo(to: Info): Conversion = Conversion(this, to) +} - Field("java.lang.Byte.MIN_VALUE") convertTo Field("kotlin.Byte.Companion.MIN_VALUE") - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER, - Field("java.lang.Byte.MAX_VALUE") convertTo Field("kotlin.Byte.Companion.MAX_VALUE") - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER, - Field("java.lang.Short.MIN_VALUE") convertTo Field("kotlin.Short.Companion.MIN_VALUE") - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER, - Field("java.lang.Short.MAX_VALUE") convertTo Field("kotlin.Short.Companion.MAX_VALUE") - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER, - Field("java.lang.Integer.MIN_VALUE") convertTo Field("kotlin.Int.Companion.MIN_VALUE") - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER, - Field("java.lang.Integer.MAX_VALUE") convertTo Field("kotlin.Int.Companion.MAX_VALUE") - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER, - Field("java.lang.Long.MIN_VALUE") convertTo Field("kotlin.Long.Companion.MIN_VALUE") - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER, - Field("java.lang.Long.MAX_VALUE") convertTo Field("kotlin.Long.Companion.MAX_VALUE") - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER, - Field("java.lang.Float.MIN_VALUE") convertTo Field("kotlin.Float.Companion.MIN_VALUE") - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER, - Field("java.lang.Float.MAX_VALUE") convertTo Field("kotlin.Float.Companion.MAX_VALUE") - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER, - Field("java.lang.Float.POSITIVE_INFINITY") convertTo Field("kotlin.Float.Companion.POSITIVE_INFINITY") - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER, - Field("java.lang.Float.NEGATIVE_INFINITY") convertTo Field("kotlin.Float.Companion.NEGATIVE_INFINITY") - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER, - Field("java.lang.Float.NaN") convertTo Field("kotlin.Float.Companion.NaN") - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER, - Field("java.lang.Double.MIN_VALUE") convertTo Field("kotlin.Double.Companion.MIN_VALUE") - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER, - Field("java.lang.Double.MAX_VALUE") convertTo Field("kotlin.Double.Companion.MAX_VALUE") - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER, - Field("java.lang.Double.POSITIVE_INFINITY") convertTo Field("kotlin.Double.Companion.POSITIVE_INFINITY") - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER, - Field("java.lang.Double.NEGATIVE_INFINITY") convertTo Field("kotlin.Double.Companion.NEGATIVE_INFINITY") - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER, - Field("java.lang.Double.NaN") convertTo Field("kotlin.Double.Companion.NaN") - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER, +private data class Method(override val fqName: String, val parameterTypesFqNames: List? = null) : SymbolInfo - Method("java.lang.Character.toUpperCase") convertTo ExtensionMethod("kotlin.text.uppercaseChar") - sinceKotlin ApiVersion.KOTLIN_1_5 - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER, - Method("java.lang.Character.toLowerCase") convertTo ExtensionMethod("kotlin.text.lowercaseChar") - sinceKotlin ApiVersion.KOTLIN_1_5 - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER, - Method("java.lang.Character.toTitleCase") convertTo ExtensionMethod("kotlin.text.titlecaseChar") - sinceKotlin ApiVersion.KOTLIN_1_5 - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER, +private data class NewExpression(override val fqName: String) : SymbolInfo - Method("java.lang.Character.digit") convertTo CustomExpression { expression -> - val arguments = (expression as JKCallExpression).arguments.arguments - if (arguments.size != 2) return@CustomExpression expression +private data class Field(override val fqName: String) : SymbolInfo - val digit = arguments[0]::value.detached() - val radix = arguments[1]::value.detached() - val argumentList = if (radix is JKLiteralExpression && radix.literal == "10") JKArgumentList() else JKArgumentList(radix) - JKBinaryExpression( - JKQualifiedExpression( - digit, - JKCallExpressionImpl( - symbolProvider.provideMethodSymbol("kotlin.text.digitToIntOrNull"), - argumentList +private data class ExtensionMethod(override val fqName: String) : SymbolInfo + +private data class CustomExpression(val expressionBuilder: (JKExpression) -> JKExpression) : Info + +private class ConversionsHolder(private val symbolProvider: JKSymbolProvider, private val typeFactory: JKTypeFactory) { + fun getConversions(): Map> { + val conversions = primitiveConversions + + objectConversions + + collectionConversions + + enumConversions + + throwableConversions + + stringConversions + + arrayConversions + + printlnConversions + return conversions.groupBy { it.from.fqName } + } + + private val neutralLocaleFQNames: List = listOf( + "java.util.Locale.ROOT", + "java.util.Locale.US", + "java.util.Locale.ENGLISH" + ) + + private val primitiveConversions: List = listOf( + Method("java.lang.Boolean.valueOf") convertTo valueOfReplacement() + withReplaceType REPLACE_WITH_QUALIFIER, + Method("java.lang.Byte.valueOf") convertTo valueOfReplacement() + withReplaceType REPLACE_WITH_QUALIFIER, + Method("java.lang.Short.valueOf") convertTo valueOfReplacement() + withReplaceType REPLACE_WITH_QUALIFIER, + Method("java.lang.Integer.valueOf") convertTo valueOfReplacement() + withReplaceType REPLACE_WITH_QUALIFIER, + Method("java.lang.Long.valueOf") convertTo valueOfReplacement() + withReplaceType REPLACE_WITH_QUALIFIER, + Method("java.lang.Float.valueOf") convertTo valueOfReplacement() + withReplaceType REPLACE_WITH_QUALIFIER, + Method("java.lang.Double.valueOf") convertTo valueOfReplacement() + withReplaceType REPLACE_WITH_QUALIFIER, + + Method("java.lang.Boolean.parseBoolean") convertTo ExtensionMethod("kotlin.text.toBoolean") + withReplaceType REPLACE_WITH_QUALIFIER, + Method("java.lang.Byte.parseByte") convertTo ExtensionMethod("kotlin.text.toByte") + withReplaceType REPLACE_WITH_QUALIFIER, + Method("java.lang.Short.parseShort") convertTo ExtensionMethod("kotlin.text.toShort") + withReplaceType REPLACE_WITH_QUALIFIER, + Method("java.lang.Integer.parseInt") convertTo ExtensionMethod("kotlin.text.toInt") + withReplaceType REPLACE_WITH_QUALIFIER + withByArgumentsFilter { it.size <= 2 }, + Method("java.lang.Long.parseLong") convertTo ExtensionMethod("kotlin.text.toLong") + withReplaceType REPLACE_WITH_QUALIFIER + withByArgumentsFilter { it.size <= 2 }, + Method("java.lang.Float.parseFloat") convertTo ExtensionMethod("kotlin.text.toFloat") + withReplaceType REPLACE_WITH_QUALIFIER, + Method("java.lang.Double.parseDouble") convertTo ExtensionMethod("kotlin.text.toDouble") + withReplaceType REPLACE_WITH_QUALIFIER, + + Method("java.lang.Number.byteValue") convertTo Method("kotlin.Number.toByte"), + Method("java.lang.Number.doubleValue") convertTo Method("kotlin.Number.toDouble"), + Method("java.lang.Number.floatValue") convertTo Method("kotlin.Number.toFloat"), + Method("java.lang.Number.intValue") convertTo Method("kotlin.Number.toInt"), + Method("java.lang.Number.longValue") convertTo Method("kotlin.Number.toLong"), + Method("java.lang.Number.shortValue") convertTo Method("kotlin.Number.toShort"), + + Field("java.lang.Byte.MIN_VALUE") convertTo Field("kotlin.Byte.Companion.MIN_VALUE") + withReplaceType REPLACE_WITH_QUALIFIER, + Field("java.lang.Byte.MAX_VALUE") convertTo Field("kotlin.Byte.Companion.MAX_VALUE") + withReplaceType REPLACE_WITH_QUALIFIER, + Field("java.lang.Short.MIN_VALUE") convertTo Field("kotlin.Short.Companion.MIN_VALUE") + withReplaceType REPLACE_WITH_QUALIFIER, + Field("java.lang.Short.MAX_VALUE") convertTo Field("kotlin.Short.Companion.MAX_VALUE") + withReplaceType REPLACE_WITH_QUALIFIER, + Field("java.lang.Integer.MIN_VALUE") convertTo Field("kotlin.Int.Companion.MIN_VALUE") + withReplaceType REPLACE_WITH_QUALIFIER, + Field("java.lang.Integer.MAX_VALUE") convertTo Field("kotlin.Int.Companion.MAX_VALUE") + withReplaceType REPLACE_WITH_QUALIFIER, + Field("java.lang.Long.MIN_VALUE") convertTo Field("kotlin.Long.Companion.MIN_VALUE") + withReplaceType REPLACE_WITH_QUALIFIER, + Field("java.lang.Long.MAX_VALUE") convertTo Field("kotlin.Long.Companion.MAX_VALUE") + withReplaceType REPLACE_WITH_QUALIFIER, + Field("java.lang.Float.MIN_VALUE") convertTo Field("kotlin.Float.Companion.MIN_VALUE") + withReplaceType REPLACE_WITH_QUALIFIER, + Field("java.lang.Float.MAX_VALUE") convertTo Field("kotlin.Float.Companion.MAX_VALUE") + withReplaceType REPLACE_WITH_QUALIFIER, + Field("java.lang.Float.POSITIVE_INFINITY") convertTo Field("kotlin.Float.Companion.POSITIVE_INFINITY") + withReplaceType REPLACE_WITH_QUALIFIER, + Field("java.lang.Float.NEGATIVE_INFINITY") convertTo Field("kotlin.Float.Companion.NEGATIVE_INFINITY") + withReplaceType REPLACE_WITH_QUALIFIER, + Field("java.lang.Float.NaN") convertTo Field("kotlin.Float.Companion.NaN") + withReplaceType REPLACE_WITH_QUALIFIER, + Field("java.lang.Double.MIN_VALUE") convertTo Field("kotlin.Double.Companion.MIN_VALUE") + withReplaceType REPLACE_WITH_QUALIFIER, + Field("java.lang.Double.MAX_VALUE") convertTo Field("kotlin.Double.Companion.MAX_VALUE") + withReplaceType REPLACE_WITH_QUALIFIER, + Field("java.lang.Double.POSITIVE_INFINITY") convertTo Field("kotlin.Double.Companion.POSITIVE_INFINITY") + withReplaceType REPLACE_WITH_QUALIFIER, + Field("java.lang.Double.NEGATIVE_INFINITY") convertTo Field("kotlin.Double.Companion.NEGATIVE_INFINITY") + withReplaceType REPLACE_WITH_QUALIFIER, + Field("java.lang.Double.NaN") convertTo Field("kotlin.Double.Companion.NaN") + withReplaceType REPLACE_WITH_QUALIFIER, + + Method("java.lang.Character.toUpperCase") convertTo ExtensionMethod("kotlin.text.uppercaseChar") + sinceKotlin ApiVersion.KOTLIN_1_5 + withReplaceType REPLACE_WITH_QUALIFIER, + Method("java.lang.Character.toLowerCase") convertTo ExtensionMethod("kotlin.text.lowercaseChar") + sinceKotlin ApiVersion.KOTLIN_1_5 + withReplaceType REPLACE_WITH_QUALIFIER, + Method("java.lang.Character.toTitleCase") convertTo ExtensionMethod("kotlin.text.titlecaseChar") + sinceKotlin ApiVersion.KOTLIN_1_5 + withReplaceType REPLACE_WITH_QUALIFIER, + + Method("java.lang.Character.digit") convertTo CustomExpression { expression -> + val arguments = (expression as JKCallExpression).arguments.arguments + if (arguments.size != 2) return@CustomExpression expression + + val digit = arguments[0]::value.detached() + val radix = arguments[1]::value.detached() + val argumentList = if (radix is JKLiteralExpression && radix.literal == "10") JKArgumentList() else JKArgumentList(radix) + JKBinaryExpression( + JKQualifiedExpression( + digit, + JKCallExpressionImpl( + symbolProvider.provideMethodSymbol("kotlin.text.digitToIntOrNull"), + argumentList + ) + ), + JKLiteralExpression("-1", JKLiteralExpression.LiteralType.INT), + JKKtOperatorImpl(JKOperatorToken.ELVIS, typeFactory.types.int) + ) + } + sinceKotlin ApiVersion.KOTLIN_1_5 + withReplaceType REPLACE_WITH_QUALIFIER, + ) + + private val objectConversions: List = listOf( + Method("java.lang.Object.getClass") convertTo Field("kotlin.jvm.javaClass"), + Method("java.lang.Object.notify") + convertTo castReceiverToJavaLangObject() + withReplaceType REPLACE_WITH_QUALIFIER + withFilter ::filterReceiverCastToJavaLangObject, + Method("java.lang.Object.notifyAll") + convertTo castReceiverToJavaLangObject() + withReplaceType REPLACE_WITH_QUALIFIER + withFilter ::filterReceiverCastToJavaLangObject, + Method("java.lang.Object.wait") + convertTo castReceiverToJavaLangObject() + withReplaceType REPLACE_WITH_QUALIFIER + withFilter ::filterReceiverCastToJavaLangObject, + ) + + private val collectionConversions: List = listOf( + Method("java.util.Map.entrySet") convertTo Field("kotlin.collections.Map.entries"), + Method("java.util.Map.keySet") convertTo Field("kotlin.collections.Map.keys"), + Method("java.util.Map.size") convertTo Field("kotlin.collections.Map.size"), + Method("java.util.Map.values") convertTo Field("kotlin.collections.Map.values"), + Method("java.util.Collection.size") convertTo Field("kotlin.collections.Collection.size"), + Method("java.util.Collection.remove") convertTo Method("kotlin.collections.MutableCollection.remove"), + Method("java.util.Collection.toArray") + convertTo Method("kotlin.collections.toTypedArray") + withByArgumentsFilter { it.isEmpty() }, + Method("java.util.Collection.toArray") + convertTo Method("kotlin.collections.toTypedArray") + withByArgumentsFilter { + it.singleOrNull()?.let { parameter -> + parameter.safeAs()?.identifier?.fqName == "kotlin.arrayOfNulls" + } == true + } withArgumentsProvider { JKArgumentList() }, + + Method("java.util.List.remove") convertTo Method("kotlin.collections.MutableCollection.removeAt"), + Method("java.util.Map.Entry.getKey") convertTo Field("kotlin.collections.Map.Entry.key"), + Method("java.util.Map.Entry.getValue") convertTo Field("kotlin.collections.Map.Entry.value"), + + Method("java.util.Set.of") + convertTo Method("kotlin.collections.setOf") + withByArgumentsFilter ::setOfFilter + withReplaceType REPLACE_WITH_QUALIFIER, + Method("java.util.List.of") + convertTo Method("kotlin.collections.listOf") + withByArgumentsFilter ::listOfFilter + withReplaceType REPLACE_WITH_QUALIFIER, + Method("java.util.Collections.singletonList") + convertTo Method("kotlin.collections.listOf") + withReplaceType REPLACE_WITH_QUALIFIER, + Method("java.util.Collections.singleton") + convertTo Method("kotlin.collections.setOf") + withReplaceType REPLACE_WITH_QUALIFIER, + Method("java.util.Collections.emptyList") + convertTo Method("kotlin.collections.emptyList") + withReplaceType REPLACE_WITH_QUALIFIER, + Method("java.util.Collections.emptySet") + convertTo Method("kotlin.collections.emptySet") + withReplaceType REPLACE_WITH_QUALIFIER, + Method("java.util.Collections.emptyMap") + convertTo Method("kotlin.collections.emptyMap") + withReplaceType REPLACE_WITH_QUALIFIER, + ) + + private val enumConversions: List = listOf( + Method("java.lang.Enum.name") convertTo Field("kotlin.Enum.name"), + Method("java.lang.Enum.ordinal") convertTo Field("kotlin.Enum.ordinal"), + Method("java.lang.Enum.valueOf") convertTo CustomExpression { expression -> + val arguments = (expression as JKCallExpression).arguments.arguments + if (arguments.size != 2) return@CustomExpression expression + val classLiteral = arguments[0]::value.detached() as? JKClassLiteralExpression ?: return@CustomExpression expression + val enumEntryName = arguments[1]::value.detached() + val typeElement = classLiteral::classType.detached() + JKCallExpressionImpl( + symbolProvider.provideMethodSymbol("kotlin.enumValueOf"), + JKArgumentList(enumEntryName), + JKTypeArgumentList(typeElement), + typeElement.type + ) + } withReplaceType REPLACE_WITH_QUALIFIER, + ) + + private val throwableConversions: List = listOf( + Method("java.lang.Throwable.getCause") convertTo Field("kotlin.Throwable.cause"), + Method("java.lang.Throwable.getMessage") convertTo Field("kotlin.Throwable.message"), + ) + + private val stringConversions: List = listOf( + Method("java.lang.CharSequence.length") convertTo Field("kotlin.String.length"), + Method("java.lang.CharSequence.charAt") convertTo Method("kotlin.String.get"), + Method("java.lang.String.indexOf") convertTo Method("kotlin.text.indexOf"), + Method("java.lang.String.lastIndexOf") convertTo Method("kotlin.text.lastIndexOf"), + Method("java.lang.String.getBytes") convertTo Method("kotlin.text.toByteArray") + withByArgumentsFilter { it.singleOrNull()?.calculateType(typeFactory)?.isStringType() == true } + withArgumentsProvider { arguments -> + val argument = arguments.arguments.single()::value.detached() + val call = JKCallExpressionImpl( + symbolProvider.provideMethodSymbol("kotlin.text.charset"), + JKArgumentList(argument) + ) + JKArgumentList(call) + }, + Method("java.lang.String.getBytes") convertTo Method("kotlin.text.toByteArray"), + Method("java.lang.String.valueOf") + convertTo ExtensionMethod("kotlin.Any.toString") + withReplaceType REPLACE_WITH_QUALIFIER + withByArgumentsFilter { it.isNotEmpty() && it.first().calculateType(typeFactory)?.isArrayType() == false }, + + Method("java.lang.String.getChars") + convertTo Method("kotlin.text.toCharArray") + withByArgumentsFilter { it.size == 4 } + withArgumentsProvider { argumentList -> + val srcBeginArgument = argumentList.arguments[0]::value.detached() + val srcEndArgument = argumentList.arguments[1]::value.detached() + val dstArgument = argumentList.arguments[2]::value.detached() + val dstBeginArgument = argumentList.arguments[3]::value.detached() + JKArgumentList(dstArgument, dstBeginArgument, srcBeginArgument, srcEndArgument) + }, + + Method("java.lang.String.valueOf") + convertTo Method("kotlin.String") + withReplaceType REPLACE_WITH_QUALIFIER + withByArgumentsFilter { it.isNotEmpty() && it.first().calculateType(typeFactory)?.isArrayType() == true }, + + Method("java.lang.String.copyValueOf") + convertTo Method("kotlin.String") + withReplaceType REPLACE_WITH_QUALIFIER + withByArgumentsFilter { it.isNotEmpty() && it.first().calculateType(typeFactory)?.isArrayType() == true }, + + Method("java.lang.String.replaceAll") + convertTo Method("kotlin.text.replace") + withArgumentsProvider ::convertFirstArgumentToRegex, + + Method("java.lang.String.replaceFirst") + convertTo Method("kotlin.text.replaceFirst") + withArgumentsProvider ::convertFirstArgumentToRegex, + + Method("java.lang.String.equalsIgnoreCase") + convertTo Method("kotlin.text.equals") + withArgumentsProvider { arguments -> + JKArgumentList( + arguments::arguments.detached() + JKNamedArgument( + JKLiteralExpression("true", JKLiteralExpression.LiteralType.BOOLEAN), + JKNameIdentifier("ignoreCase") + ) + ) + }, + + Method("java.lang.String.toUpperCase") convertTo Method("kotlin.text.uppercase") + sinceKotlin ApiVersion.KOTLIN_1_5 + withArgumentsProvider (::stringConversionArgumentsProvider), + Method("java.lang.String.toLowerCase") convertTo Method("kotlin.text.lowercase") + sinceKotlin ApiVersion.KOTLIN_1_5 + withArgumentsProvider (::stringConversionArgumentsProvider), + + Method("java.lang.String.compareToIgnoreCase") + convertTo Method("kotlin.text.compareTo") + withArgumentsProvider { arguments -> + JKArgumentList( + arguments::arguments.detached() + JKNamedArgument( + JKLiteralExpression("true", JKLiteralExpression.LiteralType.BOOLEAN), + JKNameIdentifier("ignoreCase") + ) + ) + }, + Method("java.lang.String.matches") + convertTo Method("kotlin.text.matches") + withArgumentsProvider ::convertFirstArgumentToRegex, + + Method("java.lang.String.regionMatches") + convertTo Method("kotlin.text.regionMatches") + withByArgumentsFilter { it.size == 5 } + withArgumentsProvider { arguments -> + val detachedArguments = arguments::arguments.detached() + JKArgumentList( + detachedArguments.drop(1) + JKNamedArgument( + detachedArguments.first()::value.detached().also { + it.clearFormatting() + }, + JKNameIdentifier("ignoreCase") + ) + ) + }, + + Method("java.lang.String.concat") convertTo + CustomExpression { expression -> + if (expression !is JKCallExpression) error("Expression should be JKCallExpression") + val firstArgument = expression.parent.cast()::receiver.detached() + val secondArgument = expression.arguments.arguments.first()::value.detached() + JKBinaryExpression( + firstArgument, + secondArgument, + JKKtOperatorImpl(JKOperatorToken.PLUS, typeFactory.types.possiblyNullString) + ) + } withReplaceType REPLACE_WITH_QUALIFIER, + + // We request the `split` function with the exact signature `split(regex: Regex, limit: Int = 0)` + // (see `JKSymbolProvider.provideMethodSymbolWithExactSignature`). + // Otherwise, `JKSymbolProvider` might choose a `split` overload with different parameters + // and `ImplicitCastsConversion` downstream will try to insert a nonsensical cast + // of the `limit` argument to Boolean. + Method("java.lang.String.split") + convertTo Method("kotlin.text.split", listOf("kotlin.text.Regex", "kotlin.Int")) + andAfter { expression -> + val arguments = + expression.cast() + .selector.cast() + .arguments + + val patternArgument = + arguments.arguments.first()::value.detached().callOn( + symbolProvider.provideMethodSymbol("kotlin.text.toRegex") + ) + + val limit: Int? = if (arguments.arguments.size == 2) { + arguments.arguments.last().value.asLiteralTextWithPrefix()?.toIntOrNull() + } else { + 0 + } + + arguments.arguments = when { + limit == null -> { + // limit is not a constant, need to make it non-negative + val limitArgument = arguments.arguments.last()::value.detached().callOn( + symbolProvider.provideMethodSymbol("kotlin.ranges.coerceAtLeast"), + listOf(JKLiteralExpression("0", JKLiteralExpression.LiteralType.INT)) + ) + listOf(JKArgumentImpl(patternArgument), JKArgumentImpl(limitArgument)) + } + + limit <= 0 -> { + // negative: same behavior as split(regex) in Kotlin + // zero or absent limit: cases are equivalent in Kotlin + listOf(JKArgumentImpl(patternArgument)) + } + + else -> { + // positive: same behavior as split(regex, limit) in Kotlin + val limitArgument = arguments.arguments.last()::value.detached() + listOf( + JKArgumentImpl(patternArgument), + JKNamedArgument(limitArgument, JKNameIdentifier("limit")) + ) + } + } + + return@andAfter if (limit == 0) { + // zero or absent limit: discard trailing empty strings to match Java behavior + expression + .callOn( + symbolProvider.provideMethodSymbol("kotlin.collections.dropLastWhile"), + listOf( + JKLambdaExpression( + JKKtItExpression(typeFactory.types.string).callOn( + symbolProvider.provideMethodSymbol("kotlin.text.isEmpty") + ).asStatement(), + emptyList() + ) + ) + ) + } else { + expression + }.castToTypedArray() + }, + + Method("java.lang.String.trim") + convertTo Method("kotlin.text.trim") + withArgumentsProvider { + JKArgumentList( + JKLambdaExpression( + JKExpressionStatement( + JKBinaryExpression( + JKFieldAccessExpression( + JKUnresolvedField( //TODO replace with `it` parameter + "it", + typeFactory + ) + ), + JKLiteralExpression("' '", JKLiteralExpression.LiteralType.CHAR), + JKKtOperatorImpl( + JKOperatorToken.LTEQ, + typeFactory.types.boolean + ) ) ), - JKLiteralExpression("-1", JKLiteralExpression.LiteralType.INT), - JKKtOperatorImpl(JKOperatorToken.ELVIS, typeFactory.types.int) + emptyList() ) + ) + }, + Method("java.lang.String.format") convertTo CustomExpression { expression -> + JKClassAccessExpression( + symbolProvider.provideClassSymbol(StandardNames.FqNames.string) + ).callOn( + symbolProvider.provideMethodSymbol("kotlin.text.String.format"), + (expression as JKCallExpression).arguments::arguments.detached() + ) + } withReplaceType REPLACE_WITH_QUALIFIER, + + // It is the constructor of "kotlin.String" and not "java.lang.String" because + // java.lang.String was already converted to kotlin.String in a previous TypeMappingConversion + NewExpression("kotlin.String") convertTo CustomExpression { newExpression -> + if (newExpression !is JKNewExpression) throw IllegalStateException() + + fun stringFactoryFunctionCall(newArguments: List = newExpression.arguments::arguments.detached()) = + JKCallExpressionImpl(symbolProvider.provideMethodSymbol("kotlin.text.String"), JKArgumentList(newArguments)) + + val arguments = newExpression.arguments.arguments + return@CustomExpression when (arguments.size) { + // `new String()` is the same as a literal empty string "" + 0 -> JKLiteralExpression("\"\"", type = STRING) + + 1 -> { + val first = arguments.first().value + if (first.calculateType(typeFactory)?.isStringType() == true) { + first.detach(arguments.first()) + if (first is JKLiteralExpression) { + // `new String("str")` is the same a literal string "str" + first + } else { + // Explicit String copy constructor call may be needed to optimize memory consumption + // See https://stackoverflow.com/a/1803312/992380 + JKTypeCastExpression( + JKNewExpression(symbolProvider.provideClassSymbol("java.lang.String"), JKArgumentList(first)), + JKTypeElement(typeFactory.types.string) + ).parenthesize() + } + } else { + stringFactoryFunctionCall() + } + } + + else -> { + val last = arguments.last().value + if ((arguments.size == 2 || arguments.size == 4) && last.calculateType(typeFactory)?.isStringType() == true) { + // the last argument is a charset in a string form, we need to convert it to a Charset object + val detachedArguments = newExpression.arguments::arguments.detached() + last.detach(arguments.last()) + val charsetCall = JKCallExpressionImpl( + symbolProvider.provideMethodSymbol("kotlin.text.charset"), + JKArgumentList(last) + ) + val newArguments = detachedArguments.dropLast(1) + listOf(JKArgumentImpl(charsetCall)) + stringFactoryFunctionCall(newArguments) + } else { + stringFactoryFunctionCall() + } + } } - sinceKotlin ApiVersion.KOTLIN_1_5 - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER, + }, + ) - Method("java.lang.Object.getClass") convertTo Field("kotlin.jvm.javaClass"), + private val arrayConversions: List = listOf( + Method("java.util.Arrays.copyOf") + convertTo ExtensionMethod("kotlin.collections.copyOf") + withByArgumentsFilter { it.size == 2 } + withReplaceType REPLACE_WITH_QUALIFIER, - Method("java.lang.Object.notify") - convertTo castReceiverToJavaLangObject() - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER - withFilter ::filterReceiverCastToJavaLangObject, - Method("java.lang.Object.notifyAll") - convertTo castReceiverToJavaLangObject() - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER - withFilter ::filterReceiverCastToJavaLangObject, - Method("java.lang.Object.wait") - convertTo castReceiverToJavaLangObject() - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER - withFilter ::filterReceiverCastToJavaLangObject, + Method("java.util.Arrays.copyOfRange") + convertTo ExtensionMethod("kotlin.collections.copyOfRange") + withByArgumentsFilter { it.size == 3 } + withReplaceType REPLACE_WITH_QUALIFIER, - Method("java.util.Map.entrySet") convertTo Field("kotlin.collections.Map.entries"), - Method("java.util.Map.keySet") convertTo Field("kotlin.collections.Map.keys"), - Method("java.util.Map.size") convertTo Field("kotlin.collections.Map.size"), - Method("java.util.Map.values") convertTo Field("kotlin.collections.Map.values"), - Method("java.util.Collection.size") convertTo Field("kotlin.collections.Collection.size"), - Method("java.util.Collection.remove") convertTo Method("kotlin.collections.MutableCollection.remove"), - Method("java.util.Collection.toArray") - convertTo Method("kotlin.collections.toTypedArray") - withByArgumentsFilter { it.isEmpty() }, - Method("java.util.Collection.toArray") - convertTo Method("kotlin.collections.toTypedArray") - withByArgumentsFilter { - it.singleOrNull()?.let { parameter -> - parameter.safeAs()?.identifier?.fqName == "kotlin.arrayOfNulls" - } == true - } withArgumentsProvider { JKArgumentList() }, + Method("java.util.Arrays.equals") + convertTo ExtensionMethod("kotlin.collections.contentEquals") + withByArgumentsFilter { it.size == 2 } + withReplaceType REPLACE_WITH_QUALIFIER, - Method("java.util.List.remove") convertTo Method("kotlin.collections.MutableCollection.removeAt"), - Method("java.util.Map.Entry.getKey") convertTo Field("kotlin.collections.Map.Entry.key"), - Method("java.util.Map.Entry.getValue") convertTo Field("kotlin.collections.Map.Entry.value"), + Method("java.util.Arrays.deepEquals") + convertTo ExtensionMethod("kotlin.collections.contentDeepEquals") + withByArgumentsFilter { it.size == 2 } + withReplaceType REPLACE_WITH_QUALIFIER, - Method("java.lang.Enum.name") convertTo Field("kotlin.Enum.name"), - Method("java.lang.Enum.ordinal") convertTo Field("kotlin.Enum.ordinal"), - Method("java.lang.Enum.valueOf") convertTo CustomExpression { expression -> - val arguments = (expression as JKCallExpression).arguments.arguments - if (arguments.size != 2) return@CustomExpression expression - val classLiteral = arguments[0]::value.detached() as? JKClassLiteralExpression ?: return@CustomExpression expression - val enumEntryName = arguments[1]::value.detached() - val typeElement = classLiteral::classType.detached() - JKCallExpressionImpl( - symbolProvider.provideMethodSymbol("kotlin.enumValueOf"), - JKArgumentList(enumEntryName), - JKTypeArgumentList(typeElement), - typeElement.type - ) - } withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER, + Method("java.util.Arrays.hashCode") + convertTo ExtensionMethod("kotlin.collections.contentHashCode") + withByArgumentsFilter { it.size == 1 } + withReplaceType REPLACE_WITH_QUALIFIER, - Method("java.lang.Throwable.getCause") convertTo Field("kotlin.Throwable.cause"), - Method("java.lang.Throwable.getMessage") convertTo Field("kotlin.Throwable.message"), + Method("java.util.Arrays.deepHashCode") + convertTo ExtensionMethod("kotlin.collections.contentDeepHashCode") + withByArgumentsFilter { it.size == 1 } + withReplaceType REPLACE_WITH_QUALIFIER, - Method("java.lang.CharSequence.length") convertTo Field("kotlin.String.length"), - Method("java.lang.CharSequence.charAt") convertTo Method("kotlin.String.get"), - Method("java.lang.String.indexOf") convertTo Method("kotlin.text.indexOf"), - Method("java.lang.String.lastIndexOf") convertTo Method("kotlin.text.lastIndexOf"), - Method("java.lang.String.getBytes") convertTo Method("kotlin.text.toByteArray") - withByArgumentsFilter { it.singleOrNull()?.calculateType(typeFactory)?.isStringType() == true } - withArgumentsProvider { arguments -> - val argument = arguments.arguments.single()::value.detached() - val call = JKCallExpressionImpl( - symbolProvider.provideMethodSymbol("kotlin.text.charset"), - JKArgumentList(argument) - ) - JKArgumentList(call) - }, - Method("java.lang.String.getBytes") convertTo Method("kotlin.text.toByteArray"), - Method("java.lang.String.valueOf") - convertTo ExtensionMethod("kotlin.Any.toString") - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER - withByArgumentsFilter { it.isNotEmpty() && it.first().calculateType(typeFactory)?.isArrayType() == false }, + Method("java.util.Arrays.toString") + convertTo ExtensionMethod("kotlin.collections.contentToString") + withByArgumentsFilter { it.size == 1 } + withReplaceType REPLACE_WITH_QUALIFIER, - Method("java.lang.String.getChars") - convertTo Method("kotlin.text.toCharArray") - withByArgumentsFilter { it.size == 4 } - withArgumentsProvider { argumentList -> - val srcBeginArgument = argumentList.arguments[0]::value.detached() - val srcEndArgument = argumentList.arguments[1]::value.detached() - val dstArgument = argumentList.arguments[2]::value.detached() - val dstBeginArgument = argumentList.arguments[3]::value.detached() - JKArgumentList(dstArgument, dstBeginArgument, srcBeginArgument, srcEndArgument) - }, + Method("java.util.Arrays.deepToString") + convertTo ExtensionMethod("kotlin.collections.contentDeepToString") + withByArgumentsFilter { it.size == 1 } + withReplaceType REPLACE_WITH_QUALIFIER, - Method("java.lang.String.valueOf") - convertTo Method("kotlin.String") - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER - withByArgumentsFilter { it.isNotEmpty() && it.first().calculateType(typeFactory)?.isArrayType() == true }, + Method("java.util.Arrays.asList") + convertTo Method("kotlin.collections.mutableListOf") + withByArgumentsFilter ::asListFilter + withReplaceType REPLACE_WITH_QUALIFIER, + ) - Method("java.lang.String.copyValueOf") - convertTo Method("kotlin.String") - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER - withByArgumentsFilter { it.isNotEmpty() && it.first().calculateType(typeFactory)?.isArrayType() == true }, + private val printlnConversions: List = listOf( + Method("java.io.PrintStream.println") + convertTo Method("kotlin.io.println") + withReplaceType REPLACE_WITH_QUALIFIER + withFilter ::isSystemOutCall, + Method("java.io.PrintStream.print") + convertTo Method("kotlin.io.print") + withReplaceType REPLACE_WITH_QUALIFIER + withFilter ::isSystemOutCall + ) - Method("java.lang.String.replaceAll") - convertTo Method("kotlin.text.replace") - withArgumentsProvider ::convertFirstArgumentToRegex, - - Method("java.lang.String.replaceFirst") - convertTo Method("kotlin.text.replaceFirst") - withArgumentsProvider ::convertFirstArgumentToRegex, - - Method("java.lang.String.equalsIgnoreCase") - convertTo Method("kotlin.text.equals") - withArgumentsProvider { arguments -> - JKArgumentList( - arguments::arguments.detached() + JKNamedArgument( - JKLiteralExpression("true", JKLiteralExpression.LiteralType.BOOLEAN), - JKNameIdentifier("ignoreCase") - ) - ) - }, - - Method("java.lang.String.toUpperCase") convertTo Method("kotlin.text.uppercase") - sinceKotlin ApiVersion.KOTLIN_1_5 - withArgumentsProvider (::stringConversionArgumentsProvider), - Method("java.lang.String.toLowerCase") convertTo Method("kotlin.text.lowercase") - sinceKotlin ApiVersion.KOTLIN_1_5 - withArgumentsProvider (::stringConversionArgumentsProvider), - - Method("java.lang.String.compareToIgnoreCase") - convertTo Method("kotlin.text.compareTo") - withArgumentsProvider { arguments -> - JKArgumentList( - arguments::arguments.detached() + JKNamedArgument( - JKLiteralExpression("true", JKLiteralExpression.LiteralType.BOOLEAN), - JKNameIdentifier("ignoreCase") - ) - ) - }, - Method("java.lang.String.matches") - convertTo Method("kotlin.text.matches") - withArgumentsProvider ::convertFirstArgumentToRegex, - - Method("java.lang.String.regionMatches") - convertTo Method("kotlin.text.regionMatches") - withByArgumentsFilter { it.size == 5 } - withArgumentsProvider { arguments -> - val detachedArguments = arguments::arguments.detached() - JKArgumentList( - detachedArguments.drop(1) + JKNamedArgument( - detachedArguments.first()::value.detached().also { - it.clearFormatting() - }, - JKNameIdentifier("ignoreCase") - ) - ) - }, - - Method("java.lang.String.concat") convertTo - CustomExpression { expression -> - if (expression !is JKCallExpression) error("Expression should be JKCallExpression") - val firstArgument = expression.parent.cast()::receiver.detached() - val secondArgument = expression.arguments.arguments.first()::value.detached() - JKBinaryExpression( - firstArgument, - secondArgument, - JKKtOperatorImpl(JKOperatorToken.PLUS, typeFactory.types.possiblyNullString) - ) - } withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER, - - // We request the `split` function with the exact signature `split(regex: Regex, limit: Int = 0)` - // (see `JKSymbolProvider.provideMethodSymbolWithExactSignature`). - // Otherwise, `JKSymbolProvider` might choose a `split` overload with different parameters - // and `ImplicitCastsConversion` downstream will try to insert a nonsensical cast - // of the `limit` argument to Boolean. - Method("java.lang.String.split") - convertTo Method("kotlin.text.split", listOf("kotlin.text.Regex", "kotlin.Int")) - andAfter { expression -> - val arguments = - expression.cast() - .selector.cast() - .arguments - - val patternArgument = - arguments.arguments.first()::value.detached().callOn( - symbolProvider.provideMethodSymbol("kotlin.text.toRegex") - ) - - val limit: Int? = if (arguments.arguments.size == 2) { - arguments.arguments.last().value.asLiteralTextWithPrefix()?.toIntOrNull() - } else { - 0 - } - - arguments.arguments = when { - limit == null -> { - // limit is not a constant, need to make it non-negative - val limitArgument = arguments.arguments.last()::value.detached().callOn( - symbolProvider.provideMethodSymbol("kotlin.ranges.coerceAtLeast"), - listOf(JKLiteralExpression("0", JKLiteralExpression.LiteralType.INT)) - ) - listOf(JKArgumentImpl(patternArgument), JKArgumentImpl(limitArgument)) - } - - limit <= 0 -> { - // negative: same behavior as split(regex) in Kotlin - // zero or absent limit: cases are equivalent in Kotlin - listOf(JKArgumentImpl(patternArgument)) - } - - else -> { - // positive: same behavior as split(regex, limit) in Kotlin - val limitArgument = arguments.arguments.last()::value.detached() - listOf( - JKArgumentImpl(patternArgument), - JKNamedArgument(limitArgument, JKNameIdentifier("limit")) - ) - } - } - - return@andAfter if (limit == 0) { - // zero or absent limit: discard trailing empty strings to match Java behavior - expression - .callOn( - symbolProvider.provideMethodSymbol("kotlin.collections.dropLastWhile"), - listOf( - JKLambdaExpression( - JKKtItExpression(typeFactory.types.string).callOn( - symbolProvider.provideMethodSymbol("kotlin.text.isEmpty") - ).asStatement(), - emptyList() - ) - ) - ) - } else { - expression - }.castToTypedArray() - }, - - Method("java.lang.String.trim") - convertTo Method("kotlin.text.trim") - withArgumentsProvider { - JKArgumentList( - JKLambdaExpression( - JKExpressionStatement( - JKBinaryExpression( - JKFieldAccessExpression( - JKUnresolvedField( //TODO replace with `it` parameter - "it", - typeFactory - ) - ), - JKLiteralExpression("' '", JKLiteralExpression.LiteralType.CHAR), - JKKtOperatorImpl( - JKOperatorToken.LTEQ, - typeFactory.types.boolean - ) - ) - ), - emptyList() - ) - ) - }, - Method("java.lang.String.format") convertTo CustomExpression { expression -> - JKClassAccessExpression( - symbolProvider.provideClassSymbol(StandardNames.FqNames.string) - ).callOn( - symbolProvider.provideMethodSymbol("kotlin.text.String.format"), - (expression as JKCallExpression).arguments::arguments.detached() - ) - } withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER, - - // It is the constructor of "kotlin.String" and not "java.lang.String" because - // java.lang.String was already converted to kotlin.String in a previous TypeMappingConversion - NewExpression("kotlin.String") convertTo CustomExpression { newExpression -> - if (newExpression !is JKNewExpression) throw IllegalStateException() - - fun stringFactoryFunctionCall(newArguments: List = newExpression.arguments::arguments.detached()) = - JKCallExpressionImpl(symbolProvider.provideMethodSymbol("kotlin.text.String"), JKArgumentList(newArguments)) - - val arguments = newExpression.arguments.arguments - return@CustomExpression when (arguments.size) { - // `new String()` is the same as a literal empty string "" - 0 -> JKLiteralExpression("\"\"", type = STRING) - - 1 -> { - val first = arguments.first().value - if (first.calculateType(typeFactory)?.isStringType() == true) { - first.detach(arguments.first()) - if (first is JKLiteralExpression) { - // `new String("str")` is the same a literal string "str" - first - } else { - // Explicit String copy constructor call may be needed to optimize memory consumption - // See https://stackoverflow.com/a/1803312/992380 - JKTypeCastExpression( - JKNewExpression(symbolProvider.provideClassSymbol("java.lang.String"), JKArgumentList(first)), - JKTypeElement(typeFactory.types.string) - ).parenthesize() - } - } else { - stringFactoryFunctionCall() - } - } - - else -> { - val last = arguments.last().value - if ((arguments.size == 2 || arguments.size == 4) && last.calculateType(typeFactory)?.isStringType() == true) { - // the last argument is a charset in a string form, we need to convert it to a Charset object - val detachedArguments = newExpression.arguments::arguments.detached() - last.detach(arguments.last()) - val charsetCall = JKCallExpressionImpl( - symbolProvider.provideMethodSymbol("kotlin.text.charset"), - JKArgumentList(last) - ) - val newArguments = detachedArguments.dropLast(1) + listOf(JKArgumentImpl(charsetCall)) - stringFactoryFunctionCall(newArguments) - } else { - stringFactoryFunctionCall() - } - } - } - }, - - Method("java.util.Arrays.copyOf") - convertTo ExtensionMethod("kotlin.collections.copyOf") - withByArgumentsFilter { it.size == 2 } - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER, - - Method("java.util.Arrays.copyOfRange") - convertTo ExtensionMethod("kotlin.collections.copyOfRange") - withByArgumentsFilter { it.size == 3 } - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER, - - Method("java.util.Arrays.equals") - convertTo ExtensionMethod("kotlin.collections.contentEquals") - withByArgumentsFilter { it.size == 2 } - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER, - - Method("java.util.Arrays.deepEquals") - convertTo ExtensionMethod("kotlin.collections.contentDeepEquals") - withByArgumentsFilter { it.size == 2 } - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER, - - Method("java.util.Arrays.hashCode") - convertTo ExtensionMethod("kotlin.collections.contentHashCode") - withByArgumentsFilter { it.size == 1 } - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER, - - Method("java.util.Arrays.deepHashCode") - convertTo ExtensionMethod("kotlin.collections.contentDeepHashCode") - withByArgumentsFilter { it.size == 1 } - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER, - - Method("java.util.Arrays.toString") - convertTo ExtensionMethod("kotlin.collections.contentToString") - withByArgumentsFilter { it.size == 1 } - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER, - - Method("java.util.Arrays.deepToString") - convertTo ExtensionMethod("kotlin.collections.contentDeepToString") - withByArgumentsFilter { it.size == 1 } - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER, - - Method("java.util.Arrays.asList") - convertTo Method("kotlin.collections.mutableListOf") - withByArgumentsFilter { containsOnlyLiterals(it) && (it.size > 1 || !containsNull(it.cast())) } - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER, - - Method("java.util.Set.of") - convertTo Method("kotlin.collections.setOf") - withByArgumentsFilter { containsOnlyLiterals(it) && containsOnlyUnique(it.cast()) && !containsNull(it.cast()) } - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER, - Method("java.util.List.of") - convertTo Method("kotlin.collections.listOf") - withByArgumentsFilter { containsOnlyLiterals(it) && !containsNull(it.cast()) } - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER, - Method("java.util.Collections.singletonList") - convertTo Method("kotlin.collections.listOf") - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER, - Method("java.util.Collections.singleton") - convertTo Method("kotlin.collections.setOf") - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER, - Method("java.util.Collections.emptyList") - convertTo Method("kotlin.collections.emptyList") - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER, - Method("java.util.Collections.emptySet") - convertTo Method("kotlin.collections.emptySet") - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER, - Method("java.util.Collections.emptyMap") - convertTo Method("kotlin.collections.emptyMap") - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER, - Method("java.io.PrintStream.println") - convertTo Method("kotlin.io.println") - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER - withFilter ::isSystemOutCall, - Method("java.io.PrintStream.print") - convertTo Method("kotlin.io.print") - withReplaceType ReplaceType.REPLACE_WITH_QUALIFIER - withFilter ::isSystemOutCall - ).groupBy { it.from.fqName } - - private fun castReceiverToJavaLangObject() = CustomExpression { expr -> + private fun castReceiverToJavaLangObject(): CustomExpression = CustomExpression { expr -> val parent = expr.parent ?: return@CustomExpression expr val (receiver, selector) = if (parent is JKQualifiedExpression) { parent.receiver.detach(parent) @@ -769,7 +796,7 @@ class BuiltinMembersConversion(context: NewJ2kConverterContext) : RecursiveAppli return (cast.type.type as? JKClassType)?.classReference?.fqName != "java.lang.Object" } - private fun valueOfReplacement() = CustomExpression { expression -> + private fun valueOfReplacement(): CustomExpression = CustomExpression { expression -> val arguments = (expression as JKCallExpression).arguments if (arguments.arguments.isEmpty()) return@CustomExpression expression val detachedArguments = arguments::arguments.detached() @@ -801,7 +828,6 @@ class BuiltinMembersConversion(context: NewJ2kConverterContext) : RecursiveAppli return JKArgumentList(listOf(JKArgumentImpl(first)) + detachedArguments.drop(1)) } - private fun JKExpression.callOn(symbol: JKMethodSymbol, arguments: List = emptyList()) = JKQualifiedExpression( this.parenthesizeIfCompoundExpression(), @@ -825,11 +851,9 @@ class BuiltinMembersConversion(context: NewJ2kConverterContext) : RecursiveAppli }?.identifier ?.deepestFqName() == "java.lang.System.out" - - private fun JKExpression.castToTypedArray() = + private fun JKExpression.castToTypedArray(): JKQualifiedExpression = callOn(symbolProvider.provideMethodSymbol("kotlin.collections.toTypedArray")) - private fun stringConversionArgumentsProvider(arguments: JKArgumentList): JKArgumentList { if (arguments.arguments.isEmpty()) { return JKArgumentList( @@ -848,13 +872,27 @@ class BuiltinMembersConversion(context: NewJ2kConverterContext) : RecursiveAppli return arguments } - private fun containsOnlyLiterals(list: List): Boolean = list.all { it is JKLiteralExpression } - private fun containsOnlyUnique(list: List): Boolean = list.size == list.distinctBy { it.literal }.size - private fun containsNull(list: List): Boolean = list.any { it.isNull() } + @Suppress("UNCHECKED_CAST") + private fun listOfFilter(arguments: List): Boolean = + arguments.containsOnlyLiterals() && !(arguments as List).containsNull() - private val neutralLocaleFQNames = listOf( - "java.util.Locale.ROOT", - "java.util.Locale.US", - "java.util.Locale.ENGLISH" - ) + @Suppress("UNCHECKED_CAST") + private fun asListFilter(arguments: List): Boolean = + arguments.containsOnlyLiterals() && + (arguments.size > 1 || !(arguments as List).containsNull()) + + @Suppress("UNCHECKED_CAST") + private fun setOfFilter(arguments: List): Boolean = + arguments.containsOnlyLiterals() && + (arguments as List).containsOnlyUnique() + && !arguments.containsNull() + + private fun List.containsOnlyLiterals(): Boolean = + all { it is JKLiteralExpression } + + private fun List.containsOnlyUnique(): Boolean = + size == distinctBy { it.literal }.size + + private fun List.containsNull(): Boolean = + any { it.isNull() } }