Fixed KT-4417 Converter from java to kotlin incorrectly places nested classes under class object

#KT-4417 Fixed
This commit is contained in:
Valentin Kipyatkov
2014-06-11 15:18:53 +04:00
parent ed290178d5
commit b00316f93a
29 changed files with 204 additions and 147 deletions
+98 -48
View File
@@ -83,18 +83,65 @@ public class Converter private(val project: Project, val settings: ConverterSett
return AnonymousClassBody(this, convertClassBody(anonymousClass), anonymousClass.getBaseClassType().resolve()?.isInterface() ?: false)
}
private fun convertClassBody(psiClass: PsiClass): List<Element> {
private fun convertClassBody(psiClass: PsiClass): ClassBody {
val membersToRemove = HashSet<PsiMember>()
val convertedMembers = LinkedHashMap<PsiElement, Element>()
val convertedElements = LinkedHashMap<PsiElement, Element>()
var inBody = false
val lBrace = psiClass.getLBrace()
for (element in psiClass.getChildren()) {
if (element == lBrace) inBody = true
if (inBody) {
convertedMembers.put(element, convertMember(element, membersToRemove))
convertedElements.put(element, convertMember(element, membersToRemove))
}
}
return convertedMembers.keySet().filter { !membersToRemove.contains(it) }.map { convertedMembers[it]!! }
for(member in membersToRemove) {
convertedElements.remove(member)
}
val membersMap = splitIntoMembers(convertedElements)
val constructors = membersMap.values().filter { it.member is Constructor }
val primaryConstructor = constructors.map { it.member }.filterIsInstance(javaClass<PrimaryConstructor>()).firstOrNull()
val secondaryConstructors = constructors.filter { it.member is SecondaryConstructor }
val normalMembers = ArrayList<MemberWithComments>()
val classObjectMembers = ArrayList<MemberWithComments>()
for((psiMember, member) in membersMap) {
if (member.member is Constructor) continue
if (!psiClass.isEnum() && psiMember !is PsiClass && psiMember.hasModifierProperty(PsiModifier.STATIC)) {
classObjectMembers.add(member)
}
else {
normalMembers.add(member)
}
}
return ClassBody(primaryConstructor,
secondaryConstructors.toMemberList(),
normalMembers.toMemberList(),
classObjectMembers.toMemberList())
}
private fun List<MemberWithComments>.toMemberList() = MemberList(flatMap { it.elements })
private fun splitIntoMembers(elements: Map<PsiElement, Element>): Map<PsiMember, MemberWithComments> {
val result = LinkedHashMap<PsiMember, MemberWithComments>()
var currentGroup = ArrayList<Element>()
var lastMember: PsiMember? = null
for ((psiElement, element) in elements) {
currentGroup.add(element)
if (element is Member) {
result.put(psiElement as PsiMember, MemberWithComments(element, currentGroup))
currentGroup = ArrayList()
lastMember = psiElement
}
}
if (lastMember != null) {
(result[lastMember]!!.elements as MutableList).addAll(currentGroup)
}
return result
}
private fun convertMember(element: PsiElement, membersToRemove: MutableSet<PsiMember>): Element = when(element) {
@@ -123,16 +170,16 @@ public class Converter private(val project: Project, val settings: ConverterSett
val implementsTypes = convertToNotNullableTypes(psiClass.getImplementsListTypes())
val extendsTypes = convertToNotNullableTypes(psiClass.getExtendsListTypes())
val name = Identifier(psiClass.getName()!!)
val classBodyElements = ArrayList(convertClassBody(psiClass))
var classBody = convertClassBody(psiClass)
when {
psiClass.isInterface() -> return Trait(this, name, getComments(psiClass), modifiers, typeParameters, extendsTypes, listOf(), implementsTypes, classBodyElements)
psiClass.isInterface() -> return Trait(this, name, getComments(psiClass), modifiers, typeParameters, extendsTypes, listOf(), implementsTypes, classBody)
psiClass.isEnum() -> return Enum(this, name, getComments(psiClass), modifiers, typeParameters, listOf(), listOf(), implementsTypes, classBodyElements)
psiClass.isEnum() -> return Enum(this, name, getComments(psiClass), modifiers, typeParameters, listOf(), listOf(), implementsTypes, classBody)
else -> {
if (psiClass.getPrimaryConstructor() == null && psiClass.getConstructors().size > 1) {
generateArtificialPrimaryConstructor(name, classBodyElements)
classBody = generateArtificialPrimaryConstructor(name, classBody)
}
val baseClassParams: List<Expression> = run {
@@ -151,46 +198,52 @@ public class Converter private(val project: Project, val settings: ConverterSett
modifiers.add(Modifier.OPEN)
}
return Class(this, name, getComments(psiClass), modifiers, typeParameters, extendsTypes, baseClassParams, implementsTypes, classBodyElements)
if (psiClass.getContainingClass() != null && !psiClass.hasModifierProperty(PsiModifier.STATIC)) {
modifiers.add(Modifier.INNER)
}
return Class(this, name, getComments(psiClass), modifiers, typeParameters, extendsTypes, baseClassParams, implementsTypes, classBody)
}
}
}
private fun generateArtificialPrimaryConstructor(className: Identifier, classBodyElements: MutableList<Element>) {
val finalOrWithEmptyInitializerFields = classBodyElements.filterIsInstance(javaClass<Field>()).filter { it.isVal || it.initializer.toKotlin().isEmpty() }
private fun generateArtificialPrimaryConstructor(className: Identifier, classBody: ClassBody): ClassBody {
assert(classBody.primaryConstructor == null)
val finalOrWithEmptyInitializerFields = classBody.normalMembers.members.filterIsInstance(javaClass<Field>()).filter { it.isVal || it.initializer.toKotlin().isEmpty() }
val initializers = HashMap<String, String>()
for (element in classBodyElements) {
if (element is SecondaryConstructor) {
for (field in finalOrWithEmptyInitializerFields) {
initializers.put(field.identifier.toKotlin(), getDefaultInitializer(field))
}
for (constructor in classBody.secondaryConstructors.members) {
constructor as SecondaryConstructor
val newStatements = ArrayList<Statement>()
for (statement in element.block!!.statements) {
var keepStatement = true
if (statement is AssignmentExpression) {
val assignee = statement.left
if (assignee is QualifiedExpression) {
for (field in finalOrWithEmptyInitializerFields) {
val id = field.identifier.toKotlin()
if (assignee.identifier.toKotlin().endsWith("." + id)) {
initializers.put(id, statement.right.toKotlin())
keepStatement = false
}
}
}
}
if (keepStatement) {
newStatements.add(statement)
}
}
newStatements.add(0, DummyStringExpression("val __ = " + createPrimaryConstructorInvocation(className.toKotlin(), finalOrWithEmptyInitializerFields, initializers)))
element.block = Block(newStatements)
for (field in finalOrWithEmptyInitializerFields) {
initializers.put(field.identifier.toKotlin(), getDefaultInitializer(field))
}
val newStatements = ArrayList<Statement>()
for (statement in constructor.block!!.statements) {
var keepStatement = true
if (statement is AssignmentExpression) {
val assignee = statement.left
if (assignee is QualifiedExpression) {
for (field in finalOrWithEmptyInitializerFields) {
val id = field.identifier.toKotlin()
if (assignee.identifier.toKotlin().endsWith("." + id)) {
initializers.put(id, statement.right.toKotlin())
keepStatement = false
}
}
}
}
if (keepStatement) {
newStatements.add(statement)
}
}
newStatements.add(0, DummyStringExpression("val __ = " + createPrimaryConstructorInvocation(className.toKotlin(), finalOrWithEmptyInitializerFields, initializers)))
constructor.block = Block(newStatements)
}
//TODO: comments?
@@ -198,12 +251,10 @@ public class Converter private(val project: Project, val settings: ConverterSett
val varValModifier = if (field.isVal) Parameter.VarValModifier.Val else Parameter.VarValModifier.Var
Parameter(field.identifier, field.`type`, varValModifier, field.modifiers.filter { ACCESS_MODIFIERS.contains(it) })
}
classBodyElements.add(PrimaryConstructor(this,
MemberComments.Empty,
setOf(Modifier.PRIVATE),
ParameterList(parameters),
Block.Empty))
classBodyElements.removeAll(finalOrWithEmptyInitializerFields)
val primaryConstructor = PrimaryConstructor(this, MemberComments.Empty, setOf(Modifier.PRIVATE), ParameterList(parameters), Block.Empty)
val updatedMembers = MemberList(classBody.normalMembers.elements.filter { !finalOrWithEmptyInitializerFields.contains(it) })
return ClassBody(primaryConstructor, classBody.secondaryConstructors, updatedMembers, classBody.classObjectMembers)
}
private fun convertInitializer(initializer: PsiClassInitializer): Initializer {
@@ -509,7 +560,6 @@ public class Converter private(val project: Project, val settings: ConverterSett
private val MODIFIERS_MAP = listOf(
PsiModifier.ABSTRACT to Modifier.ABSTRACT,
PsiModifier.STATIC to Modifier.STATIC,
PsiModifier.PUBLIC to Modifier.PUBLIC,
PsiModifier.PROTECTED to Modifier.PROTECTED,
PsiModifier.PRIVATE to Modifier.PRIVATE
@@ -19,7 +19,7 @@ package org.jetbrains.jet.j2k.ast
import org.jetbrains.jet.j2k.Converter
import java.util.Collections
class AnonymousClassBody(converter: Converter, bodyElements: List<Element>, val extendsTrait: Boolean)
class AnonymousClassBody(converter: Converter, body: ClassBody, val extendsTrait: Boolean)
: Class(converter,
Identifier(""),
MemberComments.Empty,
@@ -28,6 +28,6 @@ class AnonymousClassBody(converter: Converter, bodyElements: List<Element>, val
listOf(),
listOf(),
listOf(),
bodyElements) {
override fun toKotlin() = bodyToKotlin()
body) {
override fun toKotlin() = body.toKotlin(null)
}
+7 -29
View File
@@ -28,7 +28,7 @@ open class Class(
val extendsTypes: List<Type>,
val baseClassParams: List<Expression>,
val implementsTypes: List<Type>,
val bodyElements: List<Element>
val body: ClassBody
) : Member(comments, modifiers) {
override fun toKotlin(): String =
@@ -39,27 +39,13 @@ open class Class(
primaryConstructorSignatureToKotlin() +
implementTypesToKotlin() +
typeParameterList.whereToKotlin().withPrefix(" ") +
bodyToKotlin()
body.toKotlin(this)
protected open val keyword: String
get() = "class"
protected val classMembers: ClassMembers = ClassMembers.fromBodyElements(bodyElements)
protected open fun primaryConstructorSignatureToKotlin(): String
= classMembers.primaryConstructor?.signatureToKotlin() ?: "()"
protected fun primaryConstructorBodyToKotlin(): String {
val constructor = classMembers.primaryConstructor
if (constructor != null && !(constructor.block?.isEmpty ?: true)) {
return "\n" + constructor.bodyToKotlin() + "\n"
}
return ""
}
private fun secondaryConstructorsAsStaticInitFunctions(): MemberList {
return MemberList(classMembers.secondaryConstructors.elements.map { if (it is SecondaryConstructor) it.toInitFunction(this) else it })
}
= body.primaryConstructor?.signatureToKotlin() ?: "()"
private fun baseClassSignatureWithParams(): List<String> {
if (keyword.equals("class") && extendsTypes.size() == 1) {
@@ -91,18 +77,10 @@ open class Class(
modifierList.add(Modifier.OPEN)
}
if (modifiers.contains(Modifier.INNER)) {
modifierList.add(Modifier.INNER)
}
return modifierList.toKotlin()
}
fun bodyToKotlin(): String {
val innerBody = classMembers.nonStaticMembers.toKotlin() + primaryConstructorBodyToKotlin() + classObjectToKotlin()
return if (innerBody.trim().isNotEmpty()) " {" + innerBody + "}" else ""
}
private fun classObjectToKotlin(): String {
val secondaryConstructorsAsStaticInitFunctions = secondaryConstructorsAsStaticInitFunctions()
val staticMembers = classMembers.staticMembers
if (secondaryConstructorsAsStaticInitFunctions.isEmpty() && staticMembers.isEmpty()) return ""
return "\nclass object {${secondaryConstructorsAsStaticInitFunctions.toKotlin()}${staticMembers.toKotlin()}}"
}
}
@@ -53,7 +53,6 @@ class SecondaryConstructor(converter: Converter,
public fun toInitFunction(containingClass: Class): Function {
val modifiers = HashSet(modifiers)
modifiers.add(Modifier.STATIC)
val statements = ArrayList(block?.statements ?: listOf())
statements.add(ReturnStatement(Identifier("__")))
val block = Block(statements)
+4 -7
View File
@@ -27,12 +27,12 @@ class Enum(
extendsTypes: List<Type>,
baseClassParams: List<Expression>,
implementsTypes: List<Type>,
bodyElements: List<Element>
body: ClassBody
) : Class(converter, name, comments, modifiers, typeParameterList,
extendsTypes, baseClassParams, implementsTypes, bodyElements) {
extendsTypes, baseClassParams, implementsTypes, body) {
override fun primaryConstructorSignatureToKotlin(): String
= classMembers.primaryConstructor?.signatureToKotlin() ?: ""
= body.primaryConstructor?.signatureToKotlin() ?: ""
override fun toKotlin(): String {
return modifiersToKotlin() +
@@ -40,9 +40,6 @@ class Enum(
primaryConstructorSignatureToKotlin() +
typeParameterList.toKotlin() +
implementTypesToKotlin() +
" {" +
classMembers.allMembers.toKotlin() +
primaryConstructorBodyToKotlin() +
"}"
body.toKotlin(this)
}
}
+1 -1
View File
@@ -32,7 +32,7 @@ open class Field(
override fun toKotlin(): String {
val declaration = commentsToKotlin() + modifiersToKotlin() + (if (isVal) "val " else "var ") + identifier.toKotlin() + " : " + `type`.toKotlin()
return if (initializer.isEmpty)
declaration + (if (isVal && !isStatic() && hasWriteAccesses) "" else " = " + getDefaultInitializer(this))
declaration + (if (isVal && hasWriteAccesses) "" else " = " + getDefaultInitializer(this))
else
declaration + " = " + initializer.toKotlin()
}
+31 -38
View File
@@ -17,6 +17,12 @@
package org.jetbrains.jet.j2k.ast
import java.util.ArrayList
import com.intellij.psi.PsiClass
import org.jetbrains.jet.j2k.Converter
import java.util.HashSet
import com.intellij.psi.PsiMember
import java.util.LinkedHashMap
import com.intellij.psi.PsiElement
class MemberComments(elements: List<Element>) : WhiteSpaceSeparatedElementList(elements, WhiteSpace.NoSpace) {
class object {
@@ -25,56 +31,43 @@ class MemberComments(elements: List<Element>) : WhiteSpaceSeparatedElementList(e
}
abstract class Member(val comments: MemberComments, val modifiers: Set<Modifier>) : Element {
fun isStatic(): Boolean = modifiers.contains(Modifier.STATIC)
fun commentsToKotlin(): String = comments.toKotlin()
}
//member itself and all the elements before it in the code (comments, whitespaces)
class MemberHolder(val member: Member, val elements: List<Element>)
class MemberWithComments(val member: Member, val elements: List<Element>)
class MemberList(elements: List<Element>) : WhiteSpaceSeparatedElementList(elements, WhiteSpace.NewLine) {
val members: List<Member>
get() = elements.filter { it is Member }.map { it as Member }
}
class ClassMembers private(
class ClassBody (
val primaryConstructor: PrimaryConstructor?,
val secondaryConstructors: MemberList,
val allMembers: MemberList,
val staticMembers: MemberList,
val nonStaticMembers: MemberList) {
class object {
public fun fromBodyElements(elements: List<Element>): ClassMembers {
val groups = splitInGroups(elements)
val constructors = groups.filter { it.member is Constructor }
val primaryConstructor = constructors.map { it.member }.filterIsInstance(javaClass<PrimaryConstructor>()).firstOrNull()
val secondaryConstructors = constructors.filter { it.member is SecondaryConstructor }
val nonConstructors = groups.filter { it.member !is Constructor }
val staticMembers = nonConstructors.filter { it.member.isStatic() }
val nonStaticMembers = nonConstructors.filter { !it.member.isStatic() }
return ClassMembers(primaryConstructor,
secondaryConstructors.toMemberList(),
nonConstructors.toMemberList(),
staticMembers.toMemberList(),
nonStaticMembers.toMemberList())
val normalMembers: MemberList,
val classObjectMembers: MemberList) {
fun toKotlin(containingClass: Class?): String {
val innerBody = normalMembers.toKotlin() + primaryConstructorBodyToKotlin() + classObjectToKotlin(containingClass)
return if (innerBody.trim().isNotEmpty()) " {" + innerBody + "}" else ""
}
private fun primaryConstructorBodyToKotlin(): String {
val constructor = primaryConstructor
if (constructor != null && !(constructor.block?.isEmpty ?: true)) {
return "\n" + constructor.bodyToKotlin() + "\n"
}
return ""
}
private fun classObjectToKotlin(containingClass: Class?): String {
val secondaryConstructorsAsStaticInitFunctions = secondaryConstructorsAsStaticInitFunctions(containingClass)
if (secondaryConstructorsAsStaticInitFunctions.isEmpty() && classObjectMembers.isEmpty()) return ""
return "\nclass object {${secondaryConstructorsAsStaticInitFunctions.toKotlin()}${classObjectMembers.toKotlin()}}"
}
private fun secondaryConstructorsAsStaticInitFunctions(containingClass: Class?): MemberList {
return MemberList(secondaryConstructors.elements.map { if (it is SecondaryConstructor && containingClass != null) it.toInitFunction(containingClass) else it })
}
}
private fun List<MemberHolder>.toMemberList() = MemberList(flatMap { it.elements })
private fun splitInGroups(elements: List<Element>): List<MemberHolder> {
val result = ArrayList<Pair<Member, MutableList<Element>>>()
var currentGroup = ArrayList<Element>()
for (element in elements) {
currentGroup.add(element)
if (element is Member) {
result.add(element to currentGroup)
currentGroup = ArrayList()
}
}
if (result.isNotEmpty()) {
result.last!!.second.addAll(currentGroup)
}
return result map { MemberHolder(it.first, it.second) }
}
@@ -20,10 +20,10 @@ enum class Modifier(val name: String) {
PUBLIC: Modifier("public")
PROTECTED: Modifier("protected")
PRIVATE: Modifier("private")
STATIC: Modifier("static")
ABSTRACT: Modifier("abstract")
OPEN: Modifier("open")
OVERRIDE: Modifier("override")
INNER: Modifier("inner")
public fun toKotlin(): String = name
}
+2 -2
View File
@@ -28,9 +28,9 @@ class Trait(
extendsTypes: List<Type>,
baseClassParams: List<Expression>,
implementsTypes: List<Type>,
bodyElements: List<Element>
body: ClassBody
) : Class(converter, name, comments, modifiers, typeParameterList,
extendsTypes, baseClassParams, implementsTypes, bodyElements) {
extendsTypes, baseClassParams, implementsTypes, body) {
override val keyword: String
get() = "trait"
@@ -621,11 +621,26 @@ public class JavaToKotlinConverterTestGenerated extends AbstractJavaToKotlinConv
doTest("j2k/tests/testData/ast/class/genericClass.java");
}
@TestMetadata("innerClassInInterface.java")
public void testInnerClassInInterface() throws Exception {
doTest("j2k/tests/testData/ast/class/innerClassInInterface.java");
}
@TestMetadata("innerEmptyClass.java")
public void testInnerEmptyClass() throws Exception {
doTest("j2k/tests/testData/ast/class/innerEmptyClass.java");
}
@TestMetadata("innerEnum.java")
public void testInnerEnum() throws Exception {
doTest("j2k/tests/testData/ast/class/innerEnum.java");
}
@TestMetadata("innerInterface.java")
public void testInnerInterface() throws Exception {
doTest("j2k/tests/testData/ast/class/innerInterface.java");
}
@TestMetadata("innerStaticClass.java")
public void testInnerStaticClass() throws Exception {
doTest("j2k/tests/testData/ast/class/innerStaticClass.java");
@@ -0,0 +1,4 @@
//file
interface A {
class B {}
}
@@ -0,0 +1,3 @@
trait A {
public class B()
}
@@ -1,3 +1,3 @@
class A() {
class B()
inner class B()
}
@@ -0,0 +1,8 @@
//file
class A {
enum E {
A,
B,
C
}
}
@@ -0,0 +1,7 @@
class A() {
enum class E {
A
B
C
}
}
@@ -0,0 +1,4 @@
//file
class A {
interface I {}
}
@@ -0,0 +1,3 @@
class A() {
trait I
}
@@ -1,5 +1,3 @@
class S() {
class object {
class Inner()
}
class Inner()
}
+1 -1
View File
@@ -1 +1 @@
enum class A {}
enum class A
@@ -1 +1 @@
enum class A : I {}
enum class A : I
@@ -1 +1 @@
enum class A : I0, I1, I2 {}
enum class A : I0, I1, I2
+1 -1
View File
@@ -1 +1 @@
enum class Test {}
enum class Test
+1 -1
View File
@@ -1 +1 @@
private enum class Test {}
private enum class Test
+1 -1
View File
@@ -1 +1 @@
protected enum class Test {}
protected enum class Test
+1 -1
View File
@@ -1 +1 @@
public enum class Test {}
public enum class Test
@@ -1,7 +1,7 @@
package org.test
class OuterClass() {
class InnerClass()
inner class InnerClass()
}
class User() {
@@ -1,9 +1,7 @@
package demo
class Foo() {
class object {
class Bar()
}
class Bar()
}
class User() {
@@ -3,7 +3,7 @@ class Base() {
}
class A() : Base() {
class C() {
inner class C() {
fun test() {
super@A.foo()
}
@@ -4,7 +4,7 @@ class Base() {
}
class A() : Base() {
class C() {
inner class C() {
fun test() {
this@A.foo()
}