diff --git a/j2k/src/org/jetbrains/jet/j2k/AnnotationConverter.kt b/j2k/src/org/jetbrains/jet/j2k/AnnotationConverter.kt index a8b1beadef1..bb1c7bb50b8 100644 --- a/j2k/src/org/jetbrains/jet/j2k/AnnotationConverter.kt +++ b/j2k/src/org/jetbrains/jet/j2k/AnnotationConverter.kt @@ -47,15 +47,15 @@ class AnnotationConverter(private val converter: Converter) { } } - val list = annotations.map { convertAnnotation(it, owner is PsiLocalVariable) }.filterNotNull() //TODO: brackets are also needed for local classes - return Annotations(list, newLines).assignNoPrototype() + val list = annotations.map { convertAnnotation(it, owner is PsiLocalVariable, newLines) }.filterNotNull() //TODO: brackets are also needed for local classes + return Annotations(list).assignNoPrototype() } private fun convertModifiersToAnnotations(owner: PsiModifierListOwner): Annotations { val list = MODIFIER_TO_ANNOTATION .filter { owner.hasModifierProperty(it.first) } - .map { org.jetbrains.jet.j2k.ast.Annotation(Identifier(it.second).assignNoPrototype(), listOf(), false).assignNoPrototype() } - return Annotations(list, false).assignNoPrototype() + .map { Annotation(Identifier(it.second).assignNoPrototype(), listOf(), false, false).assignNoPrototype() } + return Annotations(list).assignNoPrototype() } private val MODIFIER_TO_ANNOTATION = listOf( @@ -65,10 +65,10 @@ class AnnotationConverter(private val converter: Converter) { PsiModifier.TRANSIENT to "transient" ) - public fun convertAnnotation(annotation: PsiAnnotation, brackets: Boolean): org.jetbrains.jet.j2k.ast.Annotation? { + public fun convertAnnotation(annotation: PsiAnnotation, brackets: Boolean, newLineAfter: Boolean): Annotation? { val qualifiedName = annotation.getQualifiedName() if (qualifiedName == CommonClassNames.JAVA_LANG_DEPRECATED && annotation.getParameterList().getAttributes().isEmpty()) { - return org.jetbrains.jet.j2k.ast.Annotation(Identifier("deprecated").assignNoPrototype(), listOf(null to LiteralExpression("\"\"").assignNoPrototype()), brackets).assignPrototype(annotation) //TODO: insert comment + return Annotation(Identifier("deprecated").assignNoPrototype(), listOf(null to LiteralExpression("\"\"").assignNoPrototype()), brackets, newLineAfter).assignPrototype(annotation) //TODO: insert comment } val nameRef = annotation.getNameReferenceElement() @@ -87,7 +87,12 @@ class AnnotationConverter(private val converter: Converter) { attrValues.map { attrName to it } } - return org.jetbrains.jet.j2k.ast.Annotation(name, arguments, brackets).assignPrototype(annotation) + return Annotation(name, arguments, brackets, newLineAfter).assignPrototype(annotation) + } + + public fun convertAnnotationMethodDefault(method: PsiAnnotationMethod): Expression? { + val value = method.getDefaultValue() ?: return null + return convertAttributeValue(value, method.getReturnType(), false, false).single() } private fun convertAttributeValue(value: PsiAnnotationMemberValue?, expectedType: PsiType?, isVararg: Boolean, isUnnamed: Boolean): List { diff --git a/j2k/src/org/jetbrains/jet/j2k/CodeBuilder.kt b/j2k/src/org/jetbrains/jet/j2k/CodeBuilder.kt index 40e4662866c..c96f372028e 100644 --- a/j2k/src/org/jetbrains/jet/j2k/CodeBuilder.kt +++ b/j2k/src/org/jetbrains/jet/j2k/CodeBuilder.kt @@ -89,6 +89,7 @@ class CodeBuilder(private val topElement: PsiElement?) { if (topElement == null || element.prototypes!!.isEmpty()) { element.generateCode(this) + element.postGenerateCode(this) return this } @@ -135,6 +136,8 @@ class CodeBuilder(private val topElement: PsiElement?) { postfixElements.forEach { appendCommentOrWhiteSpace(it) } + element.postGenerateCode(this) + return this } diff --git a/j2k/src/org/jetbrains/jet/j2k/Converter.kt b/j2k/src/org/jetbrains/jet/j2k/Converter.kt index e49ae4a48cc..fe8089a233d 100644 --- a/j2k/src/org/jetbrains/jet/j2k/Converter.kt +++ b/j2k/src/org/jetbrains/jet/j2k/Converter.kt @@ -117,7 +117,7 @@ public class Converter private(val project: Project, is PsiExpression -> convertExpression(element) is PsiImportList -> convertImportList(element) is PsiImportStatementBase -> convertImport(element, false) - is PsiAnnotation -> annotationConverter.convertAnnotation(element, false) + is PsiAnnotation -> annotationConverter.convertAnnotation(element, false, false) is PsiPackageStatement -> PackageStatement(quoteKeywords(element.getPackageName() ?: "")).assignPrototype(element) else -> null } @@ -164,6 +164,8 @@ public class Converter private(val project: Project, val convertedMembers = LinkedHashMap() for (element in psiClass.getChildren()) { if (element is PsiMember) { + if (element is PsiAnnotationMethod) continue // converted in convertAnnotationType() + val converted = convertMember(element, membersToRemove, constructorConverter) if (converted != null && !converted.isEmpty) { convertedMembers.put(element, converted) @@ -240,6 +242,10 @@ public class Converter private(val project: Project, = annotationConverter.convertAnnotations(owner) fun convertClass(psiClass: PsiClass): Class { + if (psiClass.isAnnotationType()) { + return convertAnnotationType(psiClass) + } + val annotations = annotationConverter.convertAnnotations(psiClass) var modifiers = convertModifiers(psiClass) val typeParameters = convertTypeParameterList(psiClass.getTypeParameterList()) @@ -270,6 +276,44 @@ public class Converter private(val project: Project, }.assignPrototype(psiClass) } + private fun convertAnnotationType(psiClass: PsiClass): Class { + val paramModifiers = Modifiers(listOf(Modifier.PUBLIC)).assignNoPrototype() + val noBlankLinesInheritance = CommentsAndSpacesInheritance(blankLinesBefore = false) + val annotationMethods = psiClass.getMethods().filterIsInstance(javaClass()) + val parameters = annotationMethods + .map { method -> + val returnType = method.getReturnType() + val typeConverted = if (method == annotationMethods.last && returnType is PsiArrayType) + VarArgType(typeConverter.convertType(returnType.getComponentType(), Nullability.NotNull).assignNoPrototype()) + else + typeConverter.convertType(returnType, Nullability.NotNull) + typeConverted.assignPrototype(method.getReturnTypeElement(), noBlankLinesInheritance) + + Parameter(method.declarationIdentifier(), + typeConverted, + Parameter.VarValModifier.Val, + convertAnnotations(method), + paramModifiers, + annotationConverter.convertAnnotationMethodDefault(method)).assignPrototype(method, noBlankLinesInheritance) + } + val parameterList = ParameterList(parameters).assignNoPrototype() + val constructorSignature = PrimaryConstructorSignature(Annotations.Empty, Modifiers.Empty, parameterList).assignNoPrototype() + + // to convert fields and nested types - they are not allowed in Kotlin but we convert them and let user refactor code + var classBody = convertBody(psiClass, null) + classBody = ClassBody(constructorSignature, classBody.members, classBody.classObjectMembers, listOf(), classBody.lBrace, classBody.rBrace) + + val annotationAnnotation = Annotation(Identifier("annotation").assignNoPrototype(), listOf(), false, false).assignNoPrototype() + return Class(psiClass.declarationIdentifier(), + (convertAnnotations(psiClass) + Annotations(listOf(annotationAnnotation))).assignNoPrototype(), + convertModifiers(psiClass).without(Modifier.ABSTRACT), + TypeParameterList.Empty, + listOf(), + listOf(), + listOf(), + classBody).assignPrototype(psiClass) + } + private fun convertInitializer(initializer: PsiClassInitializer): Initializer { return Initializer(convertBlock(initializer.getBody()), convertModifiers(initializer)).assignPrototype(initializer) } @@ -606,8 +650,8 @@ public class Converter private(val project: Project, val convertedType = typeConverter.convertType(types[it], Nullability.NotNull) null to MethodCallExpression.buildNotNull(null, "javaClass", listOf(), listOf(convertedType)).assignPrototype(refElements[it]) } - val annotation = Annotation(Identifier("throws").assignNoPrototype(), arguments, false) - return Annotations(listOf(annotation.assignPrototype(throwsList)), true).assignPrototype(throwsList) + val annotation = Annotation(Identifier("throws").assignNoPrototype(), arguments, false, true) + return Annotations(listOf(annotation.assignPrototype(throwsList))).assignPrototype(throwsList) } } diff --git a/j2k/src/org/jetbrains/jet/j2k/ast/Annotation.kt b/j2k/src/org/jetbrains/jet/j2k/ast/Annotation.kt index 25d3ccf6d4f..775b608715a 100644 --- a/j2k/src/org/jetbrains/jet/j2k/ast/Annotation.kt +++ b/j2k/src/org/jetbrains/jet/j2k/ast/Annotation.kt @@ -18,7 +18,7 @@ package org.jetbrains.jet.j2k.ast import org.jetbrains.jet.j2k.* -class Annotation(val name: Identifier, val arguments: List>, val brackets: Boolean) : Element() { +class Annotation(val name: Identifier, val arguments: List>, val brackets: Boolean, val newLineAfter: Boolean) : Element() { private fun CodeBuilder.surroundWithBrackets(action: () -> Unit) { if (brackets) append("[") action() @@ -28,44 +28,45 @@ class Annotation(val name: Identifier, val arguments: List, val newLines: Boolean) : Element() { - private val br = if (newLines) "\n" else " " - +class Annotations(val annotations: List) : Element() { override fun generateCode(builder: CodeBuilder) { - if (annotations.isNotEmpty()) { - builder.append(annotations, br, "", br) - } + builder.append(annotations, "") } override val isEmpty: Boolean = annotations.isEmpty() - fun plus(other: Annotations) = Annotations(annotations + other.annotations, newLines || other.newLines) + fun plus(other: Annotations) = Annotations(annotations + other.annotations) class object { - val Empty = Annotations(listOf(), false) + val Empty = Annotations(listOf()) } } fun Annotations.withBrackets(): Annotations - = Annotations(annotations.map { Annotation(it.name, it.arguments, true).assignPrototypesFrom(it) }, newLines).assignPrototypesFrom(this) + = Annotations(annotations.map { Annotation(it.name, it.arguments, true, it.newLineAfter).assignPrototypesFrom(it) }).assignPrototypesFrom(this) diff --git a/j2k/src/org/jetbrains/jet/j2k/ast/Element.kt b/j2k/src/org/jetbrains/jet/j2k/ast/Element.kt index d7aeae03344..770eb970320 100644 --- a/j2k/src/org/jetbrains/jet/j2k/ast/Element.kt +++ b/j2k/src/org/jetbrains/jet/j2k/ast/Element.kt @@ -72,6 +72,8 @@ abstract class Element { /** This method should not be used anywhere except for CodeBuilder! Use CodeBuilder.append instead. */ public abstract fun generateCode(builder: CodeBuilder) + public open fun postGenerateCode(builder: CodeBuilder) { } + public open val isEmpty: Boolean get() = false object Empty : Element() { diff --git a/j2k/src/org/jetbrains/jet/j2k/ast/Parameter.kt b/j2k/src/org/jetbrains/jet/j2k/ast/Parameter.kt index 3a99fa907ff..38653ecec7c 100644 --- a/j2k/src/org/jetbrains/jet/j2k/ast/Parameter.kt +++ b/j2k/src/org/jetbrains/jet/j2k/ast/Parameter.kt @@ -34,7 +34,6 @@ class Parameter(val identifier: Identifier, builder.append(annotations).appendWithSpaceAfter(modifiers) if (`type` is VarArgType) { - assert(varVal == VarValModifier.None) builder.append("vararg ") } diff --git a/j2k/tests/test/org/jetbrains/jet/j2k/test/JavaToKotlinConverterTestGenerated.java b/j2k/tests/test/org/jetbrains/jet/j2k/test/JavaToKotlinConverterTestGenerated.java index d1c94ed60a3..b6ea44e19fe 100644 --- a/j2k/tests/test/org/jetbrains/jet/j2k/test/JavaToKotlinConverterTestGenerated.java +++ b/j2k/tests/test/org/jetbrains/jet/j2k/test/JavaToKotlinConverterTestGenerated.java @@ -43,6 +43,21 @@ public class JavaToKotlinConverterTestGenerated extends AbstractJavaToKotlinConv JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.TestsPackage", new File("j2k/tests/testData/ast/annotations"), Pattern.compile("^(.+)\\.java$"), true); } + @TestMetadata("annotationInterface1.java") + public void testAnnotationInterface1() throws Exception { + doTest("j2k/tests/testData/ast/annotations/annotationInterface1.java"); + } + + @TestMetadata("annotationInterface2.java") + public void testAnnotationInterface2() throws Exception { + doTest("j2k/tests/testData/ast/annotations/annotationInterface2.java"); + } + + @TestMetadata("annotationInterface3.java") + public void testAnnotationInterface3() throws Exception { + doTest("j2k/tests/testData/ast/annotations/annotationInterface3.java"); + } + @TestMetadata("annotationUsages.java") public void testAnnotationUsages() throws Exception { doTest("j2k/tests/testData/ast/annotations/annotationUsages.java"); diff --git a/j2k/tests/testData/ast/annotations/annotationInterface1.java b/j2k/tests/testData/ast/annotations/annotationInterface1.java new file mode 100644 index 00000000000..63da1005e3c --- /dev/null +++ b/j2k/tests/testData/ast/annotations/annotationInterface1.java @@ -0,0 +1,16 @@ +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +@interface Anon { + String[] stringArray(); + + int[] intArray(); + + // string + String string(); +} + +@Anon(string = "a", stringArray = { "a", "b" }, intArray = { 1, 2 }) +@Target({ElementType.CONSTRUCTOR, ElementType.FIELD}) +@interface I { +} diff --git a/j2k/tests/testData/ast/annotations/annotationInterface1.kt b/j2k/tests/testData/ast/annotations/annotationInterface1.kt new file mode 100644 index 00000000000..3119b9853e5 --- /dev/null +++ b/j2k/tests/testData/ast/annotations/annotationInterface1.kt @@ -0,0 +1,9 @@ +import java.lang.annotation.ElementType +import java.lang.annotation.Target + +annotation class Anon(public val stringArray: Array, public val intArray: IntArray, // string + public val string: String) + +Anon(string = "a", stringArray = array("a", "b"), intArray = intArray(1, 2)) +Target(ElementType.CONSTRUCTOR, ElementType.FIELD) +annotation class I diff --git a/j2k/tests/testData/ast/annotations/annotationInterface2.java b/j2k/tests/testData/ast/annotations/annotationInterface2.java new file mode 100644 index 00000000000..b4fbc8b221a --- /dev/null +++ b/j2k/tests/testData/ast/annotations/annotationInterface2.java @@ -0,0 +1,8 @@ +@interface Anon { + String s() default "a"; + String[] stringArray() default { "a", "b" }; + int[] intArray(); +} + +@Anon(intArray = {1, 2}) +class A{ } diff --git a/j2k/tests/testData/ast/annotations/annotationInterface2.kt b/j2k/tests/testData/ast/annotations/annotationInterface2.kt new file mode 100644 index 00000000000..7dfb5332986 --- /dev/null +++ b/j2k/tests/testData/ast/annotations/annotationInterface2.kt @@ -0,0 +1,4 @@ +annotation class Anon(public val s: String = "a", public val stringArray: Array = array("a", "b"), public vararg val intArray: Int) + +Anon(intArray = *intArray(1, 2)) +class A \ No newline at end of file diff --git a/j2k/tests/testData/ast/annotations/annotationInterface3.java b/j2k/tests/testData/ast/annotations/annotationInterface3.java new file mode 100644 index 00000000000..736257b175f --- /dev/null +++ b/j2k/tests/testData/ast/annotations/annotationInterface3.java @@ -0,0 +1,14 @@ +@interface Anon { + String value(); + + enum E { + A, B + } + + E field = E.A; +} + +@Anon("a") +interface I { + Anon.E e = Anon.field; +} diff --git a/j2k/tests/testData/ast/annotations/annotationInterface3.kt b/j2k/tests/testData/ast/annotations/annotationInterface3.kt new file mode 100644 index 00000000000..eb0e44aaeaf --- /dev/null +++ b/j2k/tests/testData/ast/annotations/annotationInterface3.kt @@ -0,0 +1,19 @@ +annotation class Anon(public val value: String) { + + public enum class E { + A + B + } + + class object { + + public val field: E = E.A + } +} + +Anon("a") +trait I { + class object { + public val e: Anon.E = Anon.field + } +} \ No newline at end of file diff --git a/j2k/tests/testData/ast/field/volatileTransientAndStrictFp.kt b/j2k/tests/testData/ast/field/volatileTransientAndStrictFp.kt index 2662a74fa42..5ec9b7764d4 100644 --- a/j2k/tests/testData/ast/field/volatileTransientAndStrictFp.kt +++ b/j2k/tests/testData/ast/field/volatileTransientAndStrictFp.kt @@ -1,7 +1,6 @@ class A { deprecated("") - volatile - var field1 = 0 + volatile var field1 = 0 transient var field2 = 1