Java to Kotlin converter: keep original placement of primary constructor body + better preserving of comments for constructor

This commit is contained in:
Valentin Kipyatkov
2014-06-25 17:44:38 +04:00
parent 88bdbb02f4
commit 947bf3c0ed
18 changed files with 184 additions and 93 deletions
+35 -15
View File
@@ -22,6 +22,7 @@ import org.jetbrains.jet.lang.psi.psiUtil.isAncestor
import java.util.ArrayList
import org.jetbrains.jet.j2k.ast.Element
import kotlin.platform.platformName
import org.jetbrains.jet.j2k.ast.CommentsAndSpacesInheritance
fun<T> CodeBuilder.append(generators: Collection<() -> T>, separator: String, prefix: String = "", suffix: String = ""): CodeBuilder {
if (generators.isNotEmpty()) {
@@ -90,14 +91,15 @@ class CodeBuilder(private val topElement: PsiElement?) {
return this
}
val notInsideElements = HashSet<PsiElement>()
val prefixElements = ArrayList<PsiElement>(1)
val postfixElements = ArrayList<PsiElement>(1)
for ((prototype, inheritBlankLinesBefore) in element.prototypes!!) {
for ((prototype, inheritance) in element.prototypes!!) {
assert(prototype !is PsiComment)
assert(prototype !is PsiWhiteSpace)
assert(topElement.isAncestor(prototype))
prefixElements.collectPrefixElements(prototype, inheritBlankLinesBefore)
postfixElements.collectPostfixElements(prototype)
prefixElements.collectPrefixElements(prototype, inheritance, notInsideElements)
postfixElements.collectPostfixElements(prototype, inheritance, notInsideElements)
}
commentsAndSpacesUsed.addAll(prefixElements)
@@ -118,14 +120,16 @@ class CodeBuilder(private val topElement: PsiElement?) {
element.generateCode(this)
// scan for all comments inside which are not yet used in the text and put them here to not loose any comment from code
for ((prototype, _) in element.prototypes!!) {
prototype.accept(object : JavaRecursiveElementVisitor(){
override fun visitComment(comment: PsiComment) {
if (commentsAndSpacesUsed.add(comment)) {
appendCommentOrWhiteSpace(comment)
for ((prototype, inheritance) in element.prototypes!!) {
if (inheritance.commentsInside) {
prototype.accept(object : JavaRecursiveElementVisitor(){
override fun visitComment(comment: PsiComment) {
if (comment !in notInsideElements && commentsAndSpacesUsed.add(comment)) {
appendCommentOrWhiteSpace(comment)
}
}
}
})
})
}
}
postfixElements.forEach { appendCommentOrWhiteSpace(it) }
@@ -133,11 +137,24 @@ class CodeBuilder(private val topElement: PsiElement?) {
return this
}
private fun MutableList<PsiElement>.collectPrefixElements(element: PsiElement, allowBlankLinesBefore: Boolean) {
val atStart = ArrayList<PsiElement>(1).collectCommentsAndSpacesAtStart(element)
private fun MutableList<PsiElement>.collectPrefixElements(element: PsiElement,
inheritance: CommentsAndSpacesInheritance,
notInsideElements: MutableSet<PsiElement>) {
val before = ArrayList<PsiElement>(1).collectCommentsAndSpacesBefore(element)
if (!allowBlankLinesBefore && before.lastOrNull() is PsiWhiteSpace) {
val atStart = ArrayList<PsiElement>(1).collectCommentsAndSpacesAtStart(element)
notInsideElements.addAll(atStart)
if (!inheritance.blankLinesBefore && !inheritance.commentsBefore) return
val firstSpace = before.lastOrNull() as? PsiWhiteSpace
if (!inheritance.commentsBefore) { // take only first whitespace
if (firstSpace != null) {
add(firstSpace)
}
return
}
if (!inheritance.blankLinesBefore && firstSpace != null) {
before.remove(before.size - 1)
}
@@ -145,8 +162,11 @@ class CodeBuilder(private val topElement: PsiElement?) {
addAll(atStart)
}
private fun MutableList<PsiElement>.collectPostfixElements(element: PsiElement) {
private fun MutableList<PsiElement>.collectPostfixElements(element: PsiElement, inheritance: CommentsAndSpacesInheritance, notInsideElements: MutableSet<PsiElement>) {
val atEnd = ArrayList<PsiElement>(1).collectCommentsAndSpacesAtEnd(element)
notInsideElements.addAll(atEnd)
if (!inheritance.commentsAfter) return
val after = ArrayList<PsiElement>(1).collectCommentsAndSpacesAfter(element)
if (after.isNotEmpty()) {
+21 -14
View File
@@ -127,21 +127,27 @@ public class Converter private(val project: Project, val settings: ConverterSett
val useClassObject = shouldGenerateClassObject(psiClass, convertedMembers)
val normalMembers = ArrayList<Member>()
val members = ArrayList<Member>()
val classObjectMembers = ArrayList<Member>()
for ((psiMember, member) in convertedMembers) {
if (member is Constructor) continue
if (useClassObject && psiMember !is PsiClass && psiMember.hasModifierProperty(PsiModifier.STATIC)) {
if (member is SecondaryConstructor) continue
if (member is PrimaryConstructor) {
val initializer = member.initializer()
if (initializer != null) {
members.add(initializer)
}
}
else if (useClassObject && psiMember !is PsiClass && psiMember.hasModifierProperty(PsiModifier.STATIC)) {
classObjectMembers.add(member)
}
else {
normalMembers.add(member)
members.add(member)
}
}
val lBrace = LBrace().assignPrototype(psiClass.getLBrace())
val rBrace = RBrace().assignPrototype(psiClass.getRBrace())
return ClassBody(primaryConstructor, secondaryConstructors, normalMembers, classObjectMembers, lBrace, rBrace)
return ClassBody(primaryConstructor?.signature(), members, secondaryConstructors, classObjectMembers, lBrace, rBrace)
}
// do not convert private static methods into class object if possible
@@ -211,9 +217,9 @@ public class Converter private(val project: Project, val settings: ConverterSett
}
private fun generateArtificialPrimaryConstructor(className: Identifier, classBody: ClassBody): ClassBody {
assert(classBody.primaryConstructor == null)
assert(classBody.primaryConstructorSignature == null)
val finalOrWithEmptyInitializerFields = classBody.normalMembers.filterIsInstance(javaClass<Field>()).filter { it.isVal || it.initializer.isEmpty }
val finalOrWithEmptyInitializerFields = classBody.members.filterIsInstance(javaClass<Field>()).filter { it.isVal || it.initializer.isEmpty }
val initializers = HashMap<Field, Expression>()
for (constructor in classBody.secondaryConstructors) {
for (field in finalOrWithEmptyInitializerFields) {
@@ -256,7 +262,6 @@ public class Converter private(val project: Project, val settings: ConverterSett
constructor.block = Block(newStatements, LBrace().assignNoPrototype(), RBrace().assignNoPrototype()).assignNoPrototype()
}
//TODO: comments?
val parameters = finalOrWithEmptyInitializerFields.map { field ->
val varValModifier = if (field.isVal) Parameter.VarValModifier.Val else Parameter.VarValModifier.Var
Parameter(field.identifier, field.`type`, varValModifier, field.annotations, field.modifiers.filter { it in ACCESS_MODIFIERS }).assignPrototypesFrom(field)
@@ -264,9 +269,9 @@ public class Converter private(val project: Project, val settings: ConverterSett
val modifiers = Modifiers(listOf(Modifier.PRIVATE)).assignNoPrototype()
val parameterList = ParameterList(parameters).assignNoPrototype()
val primaryConstructor = PrimaryConstructor(this, Annotations.Empty, modifiers, parameterList, Block.Empty)
val updatedMembers = classBody.normalMembers.filter { !finalOrWithEmptyInitializerFields.contains(it) }
return ClassBody(primaryConstructor, classBody.secondaryConstructors, updatedMembers, classBody.classObjectMembers, classBody.lBrace, classBody.rBrace)
val constructorSignature = PrimaryConstructorSignature(modifiers, parameterList).assignNoPrototype()
val updatedMembers = classBody.members.filter { !finalOrWithEmptyInitializerFields.contains(it) }
return ClassBody(constructorSignature, updatedMembers, classBody.secondaryConstructors, classBody.classObjectMembers, classBody.lBrace, classBody.rBrace)
}
private fun convertInitializer(initializer: PsiClassInitializer): Initializer {
@@ -491,7 +496,7 @@ public class Converter private(val project: Project, val settings: ConverterSett
`type`,
if (isVal(field)) Parameter.VarValModifier.Val else Parameter.VarValModifier.Var,
convertAnnotations(parameter) + convertAnnotations(field),
convertModifiers(field).filter { it in ACCESS_MODIFIERS }).assignPrototypes(listOf(parameter, field), inheritBlankLinesBefore = false)
convertModifiers(field).filter { it in ACCESS_MODIFIERS }).assignPrototypes(listOf(parameter, field), CommentsAndSpacesInheritance(blankLinesBefore = false))
}
}).assignPrototype(constructor.getParameterList())
return PrimaryConstructor(this, annotations, modifiers, parameterList, block).assignPrototype(constructor)
@@ -654,8 +659,10 @@ public class Converter private(val project: Project, val settings: ConverterSett
return Identifier(identifier.getText()!!).assignPrototype(identifier)
}
fun convertModifiers(owner: PsiModifierListOwner): Modifiers
= Modifiers(MODIFIERS_MAP.filter { owner.hasModifierProperty(it.first) }.map { it.second }).assignPrototype(owner.getModifierList(), false)
fun convertModifiers(owner: PsiModifierListOwner): Modifiers {
return Modifiers(MODIFIERS_MAP.filter { owner.hasModifierProperty(it.first) }.map { it.second })
.assignPrototype(owner.getModifierList(), CommentsAndSpacesInheritance(blankLinesBefore = false))
}
private val MODIFIERS_MAP = listOf(
PsiModifier.ABSTRACT to Modifier.ABSTRACT,
+6 -5
View File
@@ -36,7 +36,12 @@ open class Class(
.append(" ")
.append(name)
.append(typeParameterList)
appendPrimaryConstructorSignature(builder)
if (body.primaryConstructorSignature != null) {
builder.append(body.primaryConstructorSignature)
}
else if (this !is Trait) { //TODO: drop this
builder.append("()")
}
appendBaseTypes(builder)
typeParameterList.appendWhere(builder)
body.append(builder, this)
@@ -45,10 +50,6 @@ open class Class(
protected open val keyword: String
get() = "class"
protected open fun appendPrimaryConstructorSignature(builder: CodeBuilder) {
body.primaryConstructor?.appendSignature(builder) ?: builder.append("()")
}
protected fun appendBaseTypes(builder: CodeBuilder) {
builder.append(baseClassSignatureWithParams(builder) + implementsTypes.map { { builder.append(it) } }, ", ", ":")
}
+5 -16
View File
@@ -21,36 +21,25 @@ import org.jetbrains.jet.j2k.*
abstract class Member(val annotations: Annotations, val modifiers: Modifiers) : Element()
class ClassBody (
val primaryConstructor: PrimaryConstructor?,
val primaryConstructorSignature: PrimaryConstructorSignature?,
val members: List<Member>,
val secondaryConstructors: List<SecondaryConstructor>,
val normalMembers: List<Member>,
val classObjectMembers: List<Member>,
val lBrace: LBrace,
val rBrace: RBrace) {
fun append(builder: CodeBuilder, containingClass: Class?) {
if (normalMembers.isEmpty() && classObjectMembers.isEmpty() && secondaryConstructors.isEmpty() && (primaryConstructor?.block?.isEmpty ?: true)) return
if (members.isEmpty() && classObjectMembers.isEmpty() && secondaryConstructors.isEmpty()) return
builder append " " append lBrace append "\n"
builder.append(normalMembers, "\n")
var notEmpty = normalMembers.isNotEmpty()
builder.append(members, "\n")
notEmpty = appendPrimaryConstructorBody(builder, notEmpty) || notEmpty
appendClassObject(builder, containingClass, notEmpty)
appendClassObject(builder, containingClass, members.isNotEmpty())
builder append "\n" append rBrace
}
private fun appendPrimaryConstructorBody(builder: CodeBuilder, blankLineBefore: Boolean): Boolean {
val constructor = primaryConstructor
if (constructor == null || constructor.block?.isEmpty ?: true) return false
if (blankLineBefore) builder.append("\n\n")
constructor.appendBody(builder)
return true
}
private fun appendClassObject(builder: CodeBuilder, containingClass: Class?, blankLineBefore: Boolean) {
if (secondaryConstructors.isEmpty() && classObjectMembers.isEmpty()) return
if (blankLineBefore) builder.append("\n\n")
@@ -18,6 +18,7 @@ package org.jetbrains.jet.j2k.ast
import java.util.ArrayList
import org.jetbrains.jet.j2k.*
import com.intellij.util.IncorrectOperationException
abstract class Constructor(
converter: Converter,
@@ -25,7 +26,10 @@ abstract class Constructor(
modifiers: Modifiers,
parameterList: ParameterList,
block: Block
) : Function(converter, Identifier.Empty, annotations, modifiers, ErrorType(), TypeParameterList.Empty, parameterList, block, false)
) : Function(converter, Identifier.Empty, annotations, modifiers, ErrorType(), TypeParameterList.Empty, parameterList, block, false) {
override fun generateCode(builder: CodeBuilder) { throw IncorrectOperationException() }
}
class PrimaryConstructor(converter: Converter,
annotations: Annotations,
@@ -34,15 +38,28 @@ class PrimaryConstructor(converter: Converter,
block: Block)
: Constructor(converter, annotations, modifiers, parameterList, block) {
public fun appendSignature(builder: CodeBuilder): CodeBuilder {
public fun initializer(): Initializer? {
return if (!block!!.isEmpty)
Initializer(block!!, Modifiers.Empty).assignPrototypesFrom(this, CommentsAndSpacesInheritance(commentsBefore = false))
else
null
}
public fun signature(): PrimaryConstructorSignature {
val noBody = block!!.isEmpty
val inheritance = CommentsAndSpacesInheritance(blankLinesBefore = false, commentsAfter = noBody, commentsInside = noBody)
return PrimaryConstructorSignature(modifiers, parameterList).assignPrototypesFrom(this, inheritance)
}
}
class PrimaryConstructorSignature(val modifiers: Modifiers, val parameterList: ParameterList) : Element() {
override fun generateCode(builder: CodeBuilder) {
val accessModifier = modifiers.filter { it in ACCESS_MODIFIERS && it != Modifier.PUBLIC }
if (!accessModifier.isEmpty) {
builder append " " append accessModifier
}
return builder append "(" append parameterList append ")"
builder append "(" append parameterList append ")"
}
public fun appendBody(builder: CodeBuilder): CodeBuilder = builder.append(block!!)
}
class SecondaryConstructor(converter: Converter,
@@ -56,7 +73,7 @@ class SecondaryConstructor(converter: Converter,
val statements = ArrayList(block?.statements ?: listOf())
statements.add(ReturnStatement(tempValIdentifier()).assignNoPrototype())
val newBlock = Block(statements, block?.lBrace ?: LBrace().assignNoPrototype(), block?.rBrace ?: RBrace().assignNoPrototype())
if (this.block != null) {
if (block != null) {
newBlock.assignPrototypesFrom(block!!)
}
+14 -6
View File
@@ -19,13 +19,13 @@ package org.jetbrains.jet.j2k.ast
import org.jetbrains.jet.j2k.*
import com.intellij.psi.PsiElement
fun <TElement: Element> TElement.assignPrototype(prototype: PsiElement?, inheritBlankLinesBefore: Boolean = true): TElement {
prototypes = if (prototype != null) listOf(PrototypeInfo(prototype, inheritBlankLinesBefore)) else listOf()
fun <TElement: Element> TElement.assignPrototype(prototype: PsiElement?, inheritance: CommentsAndSpacesInheritance = CommentsAndSpacesInheritance()): TElement {
prototypes = if (prototype != null) listOf(PrototypeInfo(prototype, inheritance)) else listOf()
return this
}
fun <TElement: Element> TElement.assignPrototypes(prototypes: List<PsiElement>, inheritBlankLinesBefore: Boolean): TElement {
this.prototypes = prototypes.map { PrototypeInfo(it, inheritBlankLinesBefore) }
fun <TElement: Element> TElement.assignPrototypes(prototypes: List<PsiElement>, inheritance: CommentsAndSpacesInheritance): TElement {
this.prototypes = prototypes.map { PrototypeInfo(it, inheritance) }
return this
}
@@ -34,13 +34,21 @@ fun <TElement: Element> TElement.assignNoPrototype(): TElement {
return this
}
fun <TElement: Element> TElement.assignPrototypesFrom(element: Element): TElement {
fun <TElement: Element> TElement.assignPrototypesFrom(element: Element, inheritance: CommentsAndSpacesInheritance? = null): TElement {
prototypes = element.prototypes
if (inheritance != null) {
prototypes = prototypes?.map { PrototypeInfo(it.element, inheritance) }
}
createdAt = element.createdAt
return this
}
data class PrototypeInfo(val element: PsiElement, val inheritBlankLinesBefore: Boolean)
data class PrototypeInfo(val element: PsiElement, val commentsAndSpacesInheritance: CommentsAndSpacesInheritance)
data class CommentsAndSpacesInheritance(val blankLinesBefore: Boolean = true,
val commentsBefore: Boolean = true,
val commentsAfter: Boolean = true,
val commentsInside: Boolean = true)
fun Element.canonicalCode(): String {
val builder = CodeBuilder(null)
+3 -5
View File
@@ -30,13 +30,11 @@ class Enum(
) : Class(name, annotations, modifiers, typeParameterList,
extendsTypes, baseClassParams, implementsTypes, body) {
override fun appendPrimaryConstructorSignature(builder: CodeBuilder) {
body.primaryConstructor?.appendSignature(builder)
}
override fun generateCode(builder: CodeBuilder) {
builder append annotations appendWithSpaceAfter presentationModifiers() append "enum class " append name
appendPrimaryConstructorSignature(builder)
if (body.primaryConstructorSignature != null) {
builder.append(body.primaryConstructorSignature)
}
builder append typeParameterList
appendBaseTypes(builder)
body.append(builder, this)
@@ -31,8 +31,6 @@ class Trait(name: Identifier,
override val keyword: String
get() = "trait"
override fun appendPrimaryConstructorSignature(builder: CodeBuilder) { }
override fun presentationModifiers(): Modifiers
= modifiers.filter { it in ACCESS_MODIFIERS }
}
@@ -16,17 +16,14 @@
package org.jetbrains.jet.j2k.test;
import junit.framework.Assert;
import junit.framework.Test;
import junit.framework.TestSuite;
import java.io.File;
import java.util.regex.Pattern;
import org.jetbrains.jet.JetTestUtils;
import org.jetbrains.jet.test.InnerTestClasses;
import org.jetbrains.jet.test.TestMetadata;
import org.jetbrains.jet.j2k.test.AbstractJavaToKotlinConverterTest;
import java.io.File;
import java.util.regex.Pattern;
/** This class is generated by {@link org.jetbrains.jet.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
@SuppressWarnings("all")
@@ -760,6 +757,11 @@ public class JavaToKotlinConverterTestGenerated extends AbstractJavaToKotlinConv
doTest("j2k/tests/testData/ast/comments/comments2.java");
}
@TestMetadata("commentsForConstructors.java")
public void testCommentsForConstructors() throws Exception {
doTest("j2k/tests/testData/ast/comments/commentsForConstructors.java");
}
@TestMetadata("fieldWithEndOfLineComment.java")
public void testFieldWithEndOfLineComment() throws Exception {
doTest("j2k/tests/testData/ast/comments/fieldWithEndOfLineComment.java");
@@ -5,6 +5,10 @@ package test
public class Test(str: String) {
var myStr = "String2"
{
myStr = str
}
public fun sout(str: String) {
System.out!!.println(str)
}
@@ -21,8 +25,4 @@ public class Test(str: String) {
Test(test)
}
{
myStr = str
}
}
@@ -4,6 +4,10 @@ package test
public class Test(str: String?) {
var myStr: String? = "String2"
{
myStr = str
}
public fun sout(str: String?) {
System.out.println(str)
}
@@ -20,8 +24,4 @@ public class Test(str: String?) {
Test(test)
}
{
myStr = str
}
}
@@ -0,0 +1,25 @@
//file
class A {
private int v;
// this is a primary constructor
A(int p) {
v = 1;
} // end of primary constructor body
// this is a secondary constructor
A() {
this(1);
} // end of secondary constructor body
}
class B {
private int x;
// this constructor will disappear
B(int x) {
this.x = x;
} // end of constructor body
void foo(){}
}
@@ -0,0 +1,25 @@
class A// this is a primary constructor
(p: Int) {
private val v: Int
{
v = 1
} // end of primary constructor body
class object {
// this is a secondary constructor
fun create(): A {
val __ = A(1)
return __
} // end of secondary constructor body
}
}
class B// this constructor will disappear
(private val x: Int) // end of constructor body
{
fun foo() {
}
}
@@ -2,6 +2,11 @@ package org.test.customer
class Customer(public val _firstName: String, public val _lastName: String) {
{
doSmthBefore()
doSmthAfter()
}
public fun getFirstName(): String {
return _firstName
}
@@ -14,11 +19,6 @@ class Customer(public val _firstName: String, public val _lastName: String) {
}
private fun doSmthAfter() {
}
{
doSmthBefore()
doSmthAfter()
}
}
class CustomerBuilder() {
@@ -1,4 +1,5 @@
class C(private val field: Int) {
{
System.out.println(field)
}
+4 -4
View File
@@ -9,10 +9,6 @@ class A(private val field6: Int, private val field8: Int, a: A) {
private var field10: Int = 0
private var field11: Int = 0
fun foo() {
field3 = field2
}
{
field7 = 10
this.field9 = 10
@@ -21,4 +17,8 @@ class A(private val field6: Int, private val field8: Int, a: A) {
}
a.field11 = 10
}
fun foo() {
field3 = field2
}
}
+1 -2
View File
@@ -1,9 +1,8 @@
package demo
class C(a: Int) {
var abc = 0
{
abc = a * 2
}
var abc = 0
}
@@ -1,4 +1,5 @@
class C(private val s: String?) {
{
if (s == null) {
System.out.print("null")