[lombok] Support for fluent getters

This commit is contained in:
Andrey Zinovyev
2021-03-25 12:09:57 +03:00
committed by TeamCityServer
parent f71e08df4d
commit e4bd33eb4f
7 changed files with 141 additions and 46 deletions
@@ -0,0 +1,50 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.lombok.config
import org.jetbrains.kotlin.descriptors.DescriptorVisibility
import org.jetbrains.kotlin.descriptors.annotations.Annotated
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
import org.jetbrains.kotlin.lombok.utils.LombokNames
import org.jetbrains.kotlin.lombok.utils.getBooleanArgument
import org.jetbrains.kotlin.lombok.utils.getVisibility
import org.jetbrains.kotlin.name.FqName
abstract class AnnotationCompanion<T> {
abstract val name: FqName
abstract fun extract(annotation: AnnotationDescriptor): T
fun getOrNull(annotated: Annotated): T? =
annotated.annotations.findAnnotation(name)?.let(this::extract)
}
data class Accessors(val fluent: Boolean = false, val chain: Boolean = false) {
companion object : AnnotationCompanion<Accessors>() {
val default = Accessors()
override val name: FqName = LombokNames.ACCESSORS
override fun extract(annotation: AnnotationDescriptor): Accessors =
Accessors(
fluent = annotation.getBooleanArgument("fluent"),
chain = annotation.getBooleanArgument("chain")
)
}
}
data class Getter(val visibility: DescriptorVisibility) {
companion object : AnnotationCompanion<Getter>() {
override val name: FqName = LombokNames.GETTER
override fun extract(annotation: AnnotationDescriptor): Getter =
Getter(
visibility = getVisibility(annotation)
)
}
}
@@ -5,52 +5,56 @@
package org.jetbrains.kotlin.lombok.processor package org.jetbrains.kotlin.lombok.processor
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.load.java.structure.impl.JavaClassImpl import org.jetbrains.kotlin.load.java.structure.impl.JavaClassImpl
import org.jetbrains.kotlin.lombok.utils.LombokAnnotationNames import org.jetbrains.kotlin.lombok.config.Accessors
import org.jetbrains.kotlin.lombok.config.Getter
import org.jetbrains.kotlin.lombok.utils.createFunction import org.jetbrains.kotlin.lombok.utils.createFunction
import org.jetbrains.kotlin.lombok.utils.getVisibility
import org.jetbrains.kotlin.lombok.utils.toPreparedBase import org.jetbrains.kotlin.lombok.utils.toPreparedBase
import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.model.SimpleTypeMarker import org.jetbrains.kotlin.types.model.SimpleTypeMarker
import org.jetbrains.kotlin.types.typeUtil.isBoolean import org.jetbrains.kotlin.types.typeUtil.isBoolean
import org.jetbrains.kotlin.types.typeUtil.isPrimitiveNumberType
class GetterProcessor : Processor { class GetterProcessor : Processor {
override fun contribute(classDescriptor: ClassDescriptor, jClass: JavaClassImpl): Parts { override fun contribute(classDescriptor: ClassDescriptor, jClass: JavaClassImpl): Parts {
val clAccessors = Accessors.getOrNull(classDescriptor)
val clGetter = Getter.getOrNull(classDescriptor)
val functions = classDescriptor.unsubstitutedMemberScope.getVariableNames() val functions = classDescriptor.unsubstitutedMemberScope.getVariableNames()
.map { .map {
classDescriptor.unsubstitutedMemberScope.getContributedVariables(it, NoLookupLocation.FROM_SYNTHETIC_SCOPE).single() classDescriptor.unsubstitutedMemberScope.getContributedVariables(it, NoLookupLocation.FROM_SYNTHETIC_SCOPE).single()
}.collectWithNotNull { it.annotations.findAnnotation(LombokAnnotationNames.GETTER) } }.collectWithNotNull { Getter.getOrNull(it) ?: clGetter }
.mapNotNull { (field, annotation) -> createGetter(classDescriptor, field, annotation) } .mapNotNull { (field, annotation) -> createGetter(classDescriptor, field, annotation, clAccessors) }
// val functions = jClass.fields.collectWithNotNull { it.findAnnotation(LombokAnnotationNames.GETTER) }.mapNotNull { (field, annotation) ->
// createGetter(classDescriptor, field, annotation)
// }
return Parts(functions) return Parts(functions)
} }
private fun createGetter( private fun createGetter(
classDescriptor: ClassDescriptor, classDescriptor: ClassDescriptor,
field: PropertyDescriptor, field: PropertyDescriptor,
annotation: AnnotationDescriptor getter: Getter,
classLevelAccessors: Accessors?
): SimpleFunctionDescriptor? { ): SimpleFunctionDescriptor? {
val prefix = if (field.type.isPrimitiveBoolean()) "is" else "get" val accessors = Accessors.getOrNull(field) ?: classLevelAccessors ?: Accessors.default
val functionName = Name.identifier(prefix + toPreparedBase(field.name.identifier))
val functionName =
if (accessors.fluent) {
field.name.identifier
} else {
val prefix = if (field.type.isPrimitiveBoolean()) "is" else "get"
prefix + toPreparedBase(field.name.identifier)
}
return createFunction( return createFunction(
classDescriptor, classDescriptor,
functionName, Name.identifier(functionName),
emptyList(), emptyList(),
field.returnType, field.returnType,
visibility = getVisibility(annotation) visibility = getter.visibility
) )
} }
@@ -7,8 +7,9 @@ package org.jetbrains.kotlin.lombok.utils
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
object LombokAnnotationNames { object LombokNames {
val ACCESSORS = FqName("lombok.experimental.Accessors")
val GETTER = FqName("lombok.Getter") val GETTER = FqName("lombok.Getter")
val SETTER = FqName("lombok.Setter") val SETTER = FqName("lombok.Setter")
@@ -10,27 +10,12 @@ import org.jetbrains.kotlin.descriptors.DescriptorVisibility
import org.jetbrains.kotlin.descriptors.Visibilities import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
import org.jetbrains.kotlin.descriptors.java.JavaVisibilities import org.jetbrains.kotlin.descriptors.java.JavaVisibilities
import org.jetbrains.kotlin.load.java.structure.JavaAnnotation
import org.jetbrains.kotlin.load.java.structure.JavaEnumValueAnnotationArgument
import org.jetbrains.kotlin.load.java.structure.JavaLiteralAnnotationArgument
import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.constants.BooleanValue
import org.jetbrains.kotlin.resolve.constants.EnumValue import org.jetbrains.kotlin.resolve.constants.EnumValue
import java.lang.IllegalArgumentException
internal fun getVisibility(annotation: JavaAnnotation, field: String = "value"): DescriptorVisibility {
val value = getStringAnnotationValue(annotation, field, "PUBLIC")
val visibility = when (value) {
"PUBLIC" -> Visibilities.Public
"PROTECTED" -> Visibilities.Protected
"PRIVATE" -> Visibilities.Private
"PACKAGE" -> JavaVisibilities.PackageVisibility
else -> Visibilities.Public
}
return DescriptorVisibilities.toDescriptorVisibility(visibility)
}
internal fun getVisibility(annotation: AnnotationDescriptor, field: String = "value"): DescriptorVisibility { internal fun getVisibility(annotation: AnnotationDescriptor, field: String = "value"): DescriptorVisibility {
val value = getStringAnnotationValue(annotation, field, "PUBLIC") val value = annotation.getStringArgument(field, "PUBLIC")
val visibility = when (value) { val visibility = when (value) {
"PUBLIC" -> Visibilities.Public "PUBLIC" -> Visibilities.Public
"PROTECTED" -> Visibilities.Protected "PROTECTED" -> Visibilities.Protected
@@ -41,19 +26,8 @@ internal fun getVisibility(annotation: AnnotationDescriptor, field: String = "va
return DescriptorVisibilities.toDescriptorVisibility(visibility) return DescriptorVisibilities.toDescriptorVisibility(visibility)
} }
private fun getStringAnnotationValue(annotation: JavaAnnotation, argumentName: String, default: String): String { private fun AnnotationDescriptor.getStringArgument(argumentName: String, default: String): String {
val argument = annotation.arguments.find { it.name?.identifier == argumentName } val argument = allValueArguments[Name.identifier(argumentName)]
?: throw IllegalArgumentException("No argument '$argumentName' found in $annotation")
return when (argument) {
is JavaEnumValueAnnotationArgument -> argument.entryName?.asString() ?: default
is JavaLiteralAnnotationArgument -> argument.value?.toString() ?: default
else -> throw RuntimeException("Argument $argument is not supported")
}
}
private fun getStringAnnotationValue(annotation: AnnotationDescriptor, argumentName: String, default: String): String {
val argument = annotation.allValueArguments[Name.identifier(argumentName)]
?: return default ?: return default
return when (argument) { return when (argument) {
@@ -61,3 +35,13 @@ private fun getStringAnnotationValue(annotation: AnnotationDescriptor, argumentN
else -> throw RuntimeException("Argument $argument is not supported") else -> throw RuntimeException("Argument $argument is not supported")
} }
} }
internal fun AnnotationDescriptor.getBooleanArgument(argumentName: String, default: Boolean = false): Boolean {
val argument = allValueArguments[Name.identifier(argumentName)]
?: return default
return when (argument) {
is BooleanValue -> argument.value
else -> throw RuntimeException("Argument $argument is not supported for Boolean value")
}
}
@@ -34,5 +34,14 @@ object Test {
obj.boxedBoolean obj.boxedBoolean
obj.getBoxedBoolean() obj.getBoxedBoolean()
//shouldn't be accesible from here
// obj.getName()
}
class OverridenGetterTest : GetterTest() {
fun usage() {
getName()
}
} }
} }
@@ -0,0 +1,42 @@
//FILE: FluentTest.java
import lombok.AccessLevel;
import lombok.Getter;
import lombok.experimental.Accessors;
@Accessors(fluent = true)
public class FluentTest {
@Getter private int age = 10;
@Getter(AccessLevel.PROTECTED) private String name;
@Getter private boolean primitiveBoolean;
@Getter private Boolean boxedBoolean;
void test() {
age();
primitiveBoolean();
}
}
//FILE: test.kt
object Test {
fun usage() {
val obj = FluentTest()
val getter = obj.age()
obj.primitiveBoolean()
obj.boxedBoolean()
}
class OverridenGetterTest : FluentTest() {
fun usage() {
name()
}
}
}
@@ -29,6 +29,11 @@ public class LombokCompileTestGenerated extends AbstractLombokCompileTest {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("plugins/lombok/lombok-compiler-plugin/testData/compile"), Pattern.compile("^(.+)\\.kt$"), null, true); KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("plugins/lombok/lombok-compiler-plugin/testData/compile"), Pattern.compile("^(.+)\\.kt$"), null, true);
} }
@TestMetadata("gettersFluent.kt")
public void testGettersFluent() throws Exception {
runTest("plugins/lombok/lombok-compiler-plugin/testData/compile/gettersFluent.kt");
}
@TestMetadata("getters.kt") @TestMetadata("getters.kt")
public void testGetters() throws Exception { public void testGetters() throws Exception {
runTest("plugins/lombok/lombok-compiler-plugin/testData/compile/getters.kt"); runTest("plugins/lombok/lombok-compiler-plugin/testData/compile/getters.kt");