Fixed KT-5247 Java to Kotlin: convert java @interfaces into Kotlin annotation classes
#KT-5247 Fixed
This commit is contained in:
@@ -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<Expression> {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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<PsiMember, Member>()
|
||||
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<PsiAnnotationMethod>())
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ package org.jetbrains.jet.j2k.ast
|
||||
|
||||
import org.jetbrains.jet.j2k.*
|
||||
|
||||
class Annotation(val name: Identifier, val arguments: List<Pair<Identifier?, Expression>>, val brackets: Boolean) : Element() {
|
||||
class Annotation(val name: Identifier, val arguments: List<Pair<Identifier?, Expression>>, 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<Pair<Identifier?, Exp
|
||||
override fun generateCode(builder: CodeBuilder) {
|
||||
if (arguments.isEmpty()) {
|
||||
builder.surroundWithBrackets { builder.append(name) }
|
||||
return
|
||||
}
|
||||
else {
|
||||
builder.surroundWithBrackets {
|
||||
builder.append(name)
|
||||
.append("(")
|
||||
.append(arguments.map {
|
||||
{
|
||||
if (it.first != null) {
|
||||
builder append it.first!! append " = " append it.second
|
||||
}
|
||||
else {
|
||||
builder append it.second
|
||||
}
|
||||
}
|
||||
}, ", ")
|
||||
.append(")")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
builder.surroundWithBrackets {
|
||||
builder.append(name)
|
||||
.append("(")
|
||||
.append(arguments.map {
|
||||
{
|
||||
if (it.first != null) {
|
||||
builder append it.first!! append " = " append it.second
|
||||
}
|
||||
else {
|
||||
builder append it.second
|
||||
}
|
||||
}
|
||||
}, ", ")
|
||||
.append(")")
|
||||
}
|
||||
override fun postGenerateCode(builder: CodeBuilder) {
|
||||
// we add line break in postGenerateCode to keep comments attached to this element on the same line
|
||||
builder.append(if (newLineAfter) "\n" else " ")
|
||||
}
|
||||
}
|
||||
|
||||
class Annotations(val annotations: List<Annotation>, val newLines: Boolean) : Element() {
|
||||
private val br = if (newLines) "\n" else " "
|
||||
|
||||
class Annotations(val annotations: List<Annotation>) : 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)
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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 ")
|
||||
}
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import java.lang.annotation.ElementType
|
||||
import java.lang.annotation.Target
|
||||
|
||||
annotation class Anon(public val stringArray: Array<String>, 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
|
||||
@@ -0,0 +1,8 @@
|
||||
@interface Anon {
|
||||
String s() default "a";
|
||||
String[] stringArray() default { "a", "b" };
|
||||
int[] intArray();
|
||||
}
|
||||
|
||||
@Anon(intArray = {1, 2})
|
||||
class A{ }
|
||||
@@ -0,0 +1,4 @@
|
||||
annotation class Anon(public val s: String = "a", public val stringArray: Array<String> = array("a", "b"), public vararg val intArray: Int)
|
||||
|
||||
Anon(intArray = *intArray(1, 2))
|
||||
class A
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
class A {
|
||||
deprecated("")
|
||||
volatile
|
||||
var field1 = 0
|
||||
volatile var field1 = 0
|
||||
|
||||
transient var field2 = 1
|
||||
|
||||
|
||||
Reference in New Issue
Block a user