[Lombok K2] Support @Builder and @Singular annotations
^KT-46959 Fixed
This commit is contained in:
@@ -125,7 +125,7 @@ class FirJavaClass @FirImplementationDetail internal constructor(
|
||||
}
|
||||
|
||||
@FirBuilderDsl
|
||||
internal class FirJavaClassBuilder : FirRegularClassBuilder(), FirAnnotationContainerBuilder {
|
||||
class FirJavaClassBuilder : FirRegularClassBuilder(), FirAnnotationContainerBuilder {
|
||||
lateinit var visibility: Visibility
|
||||
var modality: Modality? = null
|
||||
var isFromSource: Boolean by Delegates.notNull()
|
||||
@@ -174,6 +174,6 @@ internal class FirJavaClassBuilder : FirRegularClassBuilder(), FirAnnotationCont
|
||||
}
|
||||
}
|
||||
|
||||
internal inline fun buildJavaClass(init: FirJavaClassBuilder.() -> Unit): FirJavaClass {
|
||||
inline fun buildJavaClass(init: FirJavaClassBuilder.() -> Unit): FirJavaClass {
|
||||
return FirJavaClassBuilder().apply(init).build()
|
||||
}
|
||||
|
||||
@@ -29,6 +29,8 @@ object LombokNames {
|
||||
val WITH_ID = ClassId.topLevel(WITH)
|
||||
val DATA_ID = ClassId.topLevel(DATA)
|
||||
val VALUE_ID = ClassId.topLevel(VALUE)
|
||||
val BUILDER_ID = ClassId.topLevel(BUILDER)
|
||||
val SINGULAR_ID = ClassId.topLevel(SINGULAR)
|
||||
val NO_ARGS_CONSTRUCTOR_ID = ClassId.topLevel(NO_ARGS_CONSTRUCTOR)
|
||||
val ALL_ARGS_CONSTRUCTOR_ID = ClassId.topLevel(ALL_ARGS_CONSTRUCTOR)
|
||||
val REQUIRED_ARGS_CONSTRUCTOR_ID = ClassId.topLevel(REQUIRED_ARGS_CONSTRUCTOR)
|
||||
|
||||
@@ -17,5 +17,6 @@ class FirLombokRegistrar(private val lombokConfigFile: File?) : FirExtensionRegi
|
||||
+::SetterGenerator
|
||||
+::WithGenerator
|
||||
+::LombokConstructorsGenerator
|
||||
+::BuilderGenerator
|
||||
}
|
||||
}
|
||||
|
||||
+13
-1
@@ -17,11 +17,13 @@ import org.jetbrains.kotlin.fir.symbols.SymbolInternals
|
||||
import org.jetbrains.kotlin.lombok.config.LombokConfig
|
||||
import org.jetbrains.kotlin.lombok.k2.config.ConeLombokAnnotations.Accessors
|
||||
import org.jetbrains.kotlin.lombok.k2.config.ConeLombokAnnotations.AllArgsConstructor
|
||||
import org.jetbrains.kotlin.lombok.k2.config.ConeLombokAnnotations.Builder
|
||||
import org.jetbrains.kotlin.lombok.k2.config.ConeLombokAnnotations.Data
|
||||
import org.jetbrains.kotlin.lombok.k2.config.ConeLombokAnnotations.Getter
|
||||
import org.jetbrains.kotlin.lombok.k2.config.ConeLombokAnnotations.NoArgsConstructor
|
||||
import org.jetbrains.kotlin.lombok.k2.config.ConeLombokAnnotations.RequiredArgsConstructor
|
||||
import org.jetbrains.kotlin.lombok.k2.config.ConeLombokAnnotations.Setter
|
||||
import org.jetbrains.kotlin.lombok.k2.config.ConeLombokAnnotations.Singular
|
||||
import org.jetbrains.kotlin.lombok.k2.config.ConeLombokAnnotations.Value
|
||||
import org.jetbrains.kotlin.lombok.k2.config.ConeLombokAnnotations.With
|
||||
import java.io.File
|
||||
@@ -34,7 +36,7 @@ class LombokService(session: FirSession, configFile: File?) : FirExtensionSessio
|
||||
}
|
||||
}
|
||||
|
||||
private val config = configFile?.let(LombokConfig::parse) ?: LombokConfig.Empty
|
||||
val config = configFile?.let(LombokConfig::parse) ?: LombokConfig.Empty
|
||||
private val cachesFactory = session.firCachesFactory
|
||||
|
||||
private val accessorsCache: Cache<Accessors> = cachesFactory.createCache { symbol ->
|
||||
@@ -77,6 +79,14 @@ class LombokService(session: FirSession, configFile: File?) : FirExtensionSessio
|
||||
Value.getOrNull(symbol.fir)
|
||||
}
|
||||
|
||||
private val builderCache: Cache<Builder?> = cachesFactory.createCache { symbol ->
|
||||
Builder.getIfAnnotated(symbol.fir, config)
|
||||
}
|
||||
|
||||
private val singularCache: Cache<Singular?> = cachesFactory.createCache { symbol ->
|
||||
Singular.getOrNull(symbol.fir)
|
||||
}
|
||||
|
||||
fun getAccessors(symbol: FirBasedSymbol<*>): Accessors = accessorsCache.getValue(symbol)
|
||||
fun getAccessorsIfAnnotated(symbol: FirBasedSymbol<*>): Accessors? = accessorsIfAnnotatedCache.getValue(symbol)
|
||||
fun getGetter(symbol: FirBasedSymbol<*>): Getter? = getterCache.getValue(symbol)
|
||||
@@ -87,6 +97,8 @@ class LombokService(session: FirSession, configFile: File?) : FirExtensionSessio
|
||||
fun getRequiredArgsConstructor(symbol: FirBasedSymbol<*>): RequiredArgsConstructor? = requiredArgsConstructorCache.getValue(symbol)
|
||||
fun getData(symbol: FirBasedSymbol<*>): Data? = dataCache.getValue(symbol)
|
||||
fun getValue(symbol: FirBasedSymbol<*>): Value? = valueCache.getValue(symbol)
|
||||
fun getBuilder(symbol: FirBasedSymbol<*>): Builder? = builderCache.getValue(symbol)
|
||||
fun getSingular(symbol: FirBasedSymbol<*>): Singular? = singularCache.getValue(symbol)
|
||||
}
|
||||
|
||||
private typealias Cache<T> = FirCache<FirBasedSymbol<*>, T, Nothing?>
|
||||
|
||||
+56
-3
@@ -9,6 +9,7 @@ import org.jetbrains.kotlin.descriptors.Visibilities
|
||||
import org.jetbrains.kotlin.descriptors.Visibility
|
||||
import org.jetbrains.kotlin.fir.FirAnnotationContainer
|
||||
import org.jetbrains.kotlin.fir.declarations.getBooleanArgument
|
||||
import org.jetbrains.kotlin.fir.declarations.getStringArgument
|
||||
import org.jetbrains.kotlin.fir.declarations.getStringArrayArgument
|
||||
import org.jetbrains.kotlin.fir.expressions.FirAnnotation
|
||||
import org.jetbrains.kotlin.fir.types.ConeClassLikeType
|
||||
@@ -16,15 +17,23 @@ import org.jetbrains.kotlin.fir.types.coneTypeSafe
|
||||
import org.jetbrains.kotlin.lombok.config.AccessLevel
|
||||
import org.jetbrains.kotlin.lombok.config.LombokConfig
|
||||
import org.jetbrains.kotlin.lombok.k2.config.LombokConfigNames.ACCESS
|
||||
import org.jetbrains.kotlin.lombok.k2.config.LombokConfigNames.BUILDER_CLASS_NAME
|
||||
import org.jetbrains.kotlin.lombok.k2.config.LombokConfigNames.BUILDER_CLASS_NAME_CONFIG
|
||||
import org.jetbrains.kotlin.lombok.k2.config.LombokConfigNames.BUILDER_METHOD_NAME
|
||||
import org.jetbrains.kotlin.lombok.k2.config.LombokConfigNames.BUILD_METHOD_NAME
|
||||
import org.jetbrains.kotlin.lombok.k2.config.LombokConfigNames.CHAIN
|
||||
import org.jetbrains.kotlin.lombok.k2.config.LombokConfigNames.CHAIN_CONFIG
|
||||
import org.jetbrains.kotlin.lombok.k2.config.LombokConfigNames.FLUENT
|
||||
import org.jetbrains.kotlin.lombok.k2.config.LombokConfigNames.FLUENT_CONFIG
|
||||
import org.jetbrains.kotlin.lombok.k2.config.LombokConfigNames.IGNORE_NULL_COLLECTIONS
|
||||
import org.jetbrains.kotlin.lombok.k2.config.LombokConfigNames.NO_IS_PREFIX_CONFIG
|
||||
import org.jetbrains.kotlin.lombok.k2.config.LombokConfigNames.PREFIX
|
||||
import org.jetbrains.kotlin.lombok.k2.config.LombokConfigNames.PREFIX_CONFIG
|
||||
import org.jetbrains.kotlin.lombok.k2.config.LombokConfigNames.SETTER_PREFIX
|
||||
import org.jetbrains.kotlin.lombok.k2.config.LombokConfigNames.STATIC_CONSTRUCTOR
|
||||
import org.jetbrains.kotlin.lombok.k2.config.LombokConfigNames.STATIC_NAME
|
||||
import org.jetbrains.kotlin.lombok.k2.config.LombokConfigNames.TO_BUILDER
|
||||
import org.jetbrains.kotlin.lombok.k2.config.LombokConfigNames.VALUE
|
||||
import org.jetbrains.kotlin.lombok.utils.LombokNames
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
|
||||
@@ -97,7 +106,7 @@ object ConeLombokAnnotations {
|
||||
class Getter(val visibility: AccessLevel = AccessLevel.PUBLIC) {
|
||||
companion object : ConeAnnotationCompanion<Getter>(LombokNames.GETTER_ID) {
|
||||
override fun extract(annotation: FirAnnotation): Getter = Getter(
|
||||
visibility = getAccessLevel(annotation)
|
||||
visibility = annotation.getAccessLevel()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -105,7 +114,7 @@ object ConeLombokAnnotations {
|
||||
class Setter(val visibility: AccessLevel = AccessLevel.PUBLIC) {
|
||||
companion object : ConeAnnotationCompanion<Setter>(LombokNames.SETTER_ID) {
|
||||
override fun extract(annotation: FirAnnotation): Setter = Setter(
|
||||
visibility = getAccessLevel(annotation)
|
||||
visibility = annotation.getAccessLevel()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -113,7 +122,7 @@ object ConeLombokAnnotations {
|
||||
class With(val visibility: AccessLevel = AccessLevel.PUBLIC) {
|
||||
companion object : ConeAnnotationCompanion<With>(LombokNames.WITH_ID) {
|
||||
override fun extract(annotation: FirAnnotation): With = With(
|
||||
visibility = getAccessLevel(annotation)
|
||||
visibility = annotation.getAccessLevel()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -188,6 +197,50 @@ object ConeLombokAnnotations {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class Builder(
|
||||
val builderClassName: String,
|
||||
val buildMethodName: String,
|
||||
val builderMethodName: String,
|
||||
val requiresToBuilder: Boolean,
|
||||
val visibility: AccessLevel,
|
||||
val setterPrefix: String?
|
||||
) {
|
||||
companion object : ConeAnnotationAndConfigCompanion<Builder>(LombokNames.BUILDER_ID) {
|
||||
private const val DEFAULT_BUILDER_CLASS_NAME = "*Builder"
|
||||
private const val DEFAULT_BUILD_METHOD_NAME = "build"
|
||||
private const val DEFAULT_BUILDER_METHOD_NAME = "builder"
|
||||
private const val DEFAULT_REQUIRES_TO_BUILDER = false
|
||||
|
||||
|
||||
override fun extract(annotation: FirAnnotation?, config: LombokConfig): Builder {
|
||||
return Builder(
|
||||
builderClassName = annotation?.getStringArgument(BUILDER_CLASS_NAME)
|
||||
?: config.getString(BUILDER_CLASS_NAME_CONFIG)
|
||||
?: DEFAULT_BUILDER_CLASS_NAME,
|
||||
buildMethodName = annotation?.getStringArgument(BUILD_METHOD_NAME) ?: DEFAULT_BUILD_METHOD_NAME,
|
||||
builderMethodName = annotation?.getStringArgument(BUILDER_METHOD_NAME) ?: DEFAULT_BUILDER_METHOD_NAME,
|
||||
requiresToBuilder = annotation?.getBooleanArgument(TO_BUILDER) ?: DEFAULT_REQUIRES_TO_BUILDER,
|
||||
visibility = annotation?.getAccessLevel(ACCESS) ?: AccessLevel.PUBLIC,
|
||||
setterPrefix = annotation?.getStringArgument(SETTER_PREFIX)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Singular(
|
||||
val singularName: String?,
|
||||
val allowNull: Boolean,
|
||||
) {
|
||||
companion object : ConeAnnotationCompanion<Singular>(LombokNames.SINGULAR_ID) {
|
||||
override fun extract(annotation: FirAnnotation): Singular {
|
||||
return Singular(
|
||||
singularName = annotation.getStringArgument(VALUE),
|
||||
allowNull = annotation.getBooleanArgument(IGNORE_NULL_COLLECTIONS) ?: false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
+11
-3
@@ -17,8 +17,8 @@ import org.jetbrains.kotlin.lombok.config.AccessLevel
|
||||
import org.jetbrains.kotlin.lombok.utils.trimToNull
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
|
||||
fun getAccessLevel(annotation: FirAnnotation, field: Name = LombokConfigNames.VALUE): AccessLevel {
|
||||
val value = annotation.getArgumentAsString(field) ?: return AccessLevel.PUBLIC
|
||||
fun FirAnnotation.getAccessLevel(field: Name = LombokConfigNames.VALUE): AccessLevel {
|
||||
val value = getArgumentAsString(field) ?: return AccessLevel.PUBLIC
|
||||
return AccessLevel.valueOf(value)
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ private fun FirAnnotation.getArgumentAsString(field: Name): String? {
|
||||
}
|
||||
|
||||
fun getVisibility(annotation: FirAnnotation, field: Name = LombokConfigNames.VALUE): Visibility {
|
||||
return getAccessLevel(annotation, field).toVisibility()
|
||||
return annotation.getAccessLevel(field).toVisibility()
|
||||
}
|
||||
|
||||
fun FirAnnotation.getNonBlankStringArgument(name: Name): String? = getStringArgument(name)?.trimToNull()
|
||||
@@ -53,9 +53,17 @@ object LombokConfigNames {
|
||||
val STATIC_NAME = Name.identifier("staticName")
|
||||
val STATIC_CONSTRUCTOR = Name.identifier("staticConstructor")
|
||||
|
||||
val BUILDER_CLASS_NAME = Name.identifier("builderClassName")
|
||||
val BUILD_METHOD_NAME = Name.identifier("buildMethodName")
|
||||
val BUILDER_METHOD_NAME = Name.identifier("builderMethodName")
|
||||
val TO_BUILDER = Name.identifier("toBuilder")
|
||||
val SETTER_PREFIX = Name.identifier("setterPrefix")
|
||||
val IGNORE_NULL_COLLECTIONS = Name.identifier("ignoreNullCollections")
|
||||
|
||||
|
||||
const val FLUENT_CONFIG = "lombok.accessors.fluent"
|
||||
const val CHAIN_CONFIG = "lombok.accessors.chain"
|
||||
const val PREFIX_CONFIG = "lombok.accessors.prefix"
|
||||
const val NO_IS_PREFIX_CONFIG = "lombok.getter.noIsPrefix"
|
||||
const val BUILDER_CLASS_NAME_CONFIG = "lombok.builder.className"
|
||||
}
|
||||
|
||||
+384
@@ -0,0 +1,384 @@
|
||||
/*
|
||||
* Copyright 2010-2022 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.k2.generators
|
||||
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import org.jetbrains.kotlin.descriptors.ClassKind
|
||||
import org.jetbrains.kotlin.descriptors.Modality
|
||||
import org.jetbrains.kotlin.descriptors.Visibility
|
||||
import org.jetbrains.kotlin.fir.FirSession
|
||||
import org.jetbrains.kotlin.fir.caches.FirCache
|
||||
import org.jetbrains.kotlin.fir.caches.createCache
|
||||
import org.jetbrains.kotlin.fir.caches.firCachesFactory
|
||||
import org.jetbrains.kotlin.fir.caches.getValue
|
||||
import org.jetbrains.kotlin.fir.containingClassForStaticMemberAttr
|
||||
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
|
||||
import org.jetbrains.kotlin.fir.declarations.builder.buildConstructedClassTypeParameterRef
|
||||
import org.jetbrains.kotlin.fir.declarations.builder.buildOuterClassTypeParameterRef
|
||||
import org.jetbrains.kotlin.fir.declarations.impl.FirResolvedDeclarationStatusImpl
|
||||
import org.jetbrains.kotlin.fir.declarations.utils.classId
|
||||
import org.jetbrains.kotlin.fir.declarations.utils.effectiveVisibility
|
||||
import org.jetbrains.kotlin.fir.extensions.FirDeclarationGenerationExtension
|
||||
import org.jetbrains.kotlin.fir.extensions.MemberGenerationContext
|
||||
import org.jetbrains.kotlin.fir.java.JavaScopeProvider
|
||||
import org.jetbrains.kotlin.fir.java.declarations.*
|
||||
import org.jetbrains.kotlin.fir.resolve.defaultType
|
||||
import org.jetbrains.kotlin.fir.resolve.providers.symbolProvider
|
||||
import org.jetbrains.kotlin.fir.symbols.SymbolInternals
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.*
|
||||
import org.jetbrains.kotlin.fir.toEffectiveVisibility
|
||||
import org.jetbrains.kotlin.fir.types.*
|
||||
import org.jetbrains.kotlin.fir.types.builder.buildResolvedTypeRef
|
||||
import org.jetbrains.kotlin.fir.types.jvm.FirJavaTypeRef
|
||||
import org.jetbrains.kotlin.load.java.structure.JavaClass
|
||||
import org.jetbrains.kotlin.load.java.structure.JavaClassifierType
|
||||
import org.jetbrains.kotlin.load.java.structure.JavaType
|
||||
import org.jetbrains.kotlin.lombok.config.LombokConfig
|
||||
import org.jetbrains.kotlin.lombok.k2.*
|
||||
import org.jetbrains.kotlin.lombok.k2.config.ConeLombokAnnotations.Builder
|
||||
import org.jetbrains.kotlin.lombok.k2.config.ConeLombokAnnotations.Singular
|
||||
import org.jetbrains.kotlin.lombok.k2.config.LombokService
|
||||
import org.jetbrains.kotlin.lombok.k2.config.lombokService
|
||||
import org.jetbrains.kotlin.lombok.k2.java.*
|
||||
import org.jetbrains.kotlin.lombok.utils.LombokNames
|
||||
import org.jetbrains.kotlin.lombok.utils.capitalize
|
||||
import org.jetbrains.kotlin.name.CallableId
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
|
||||
class BuilderGenerator(session: FirSession) : FirDeclarationGenerationExtension(session) {
|
||||
companion object {
|
||||
private const val TO_BUILDER = "toBuilder"
|
||||
}
|
||||
|
||||
private val lombokService: LombokService
|
||||
get() = session.lombokService
|
||||
|
||||
private val builderClassCache: FirCache<FirClassSymbol<*>, FirJavaClass?, Nothing?> =
|
||||
session.firCachesFactory.createCache(::createBuilderClass)
|
||||
|
||||
private val functionsCache: FirCache<FirClassSymbol<*>, Map<Name, List<FirJavaMethod>>?, Nothing?> =
|
||||
session.firCachesFactory.createCache(::createFunctions)
|
||||
|
||||
override fun getCallableNamesForClass(classSymbol: FirClassSymbol<*>): Set<Name> {
|
||||
if (!classSymbol.isSuitableJavaClass()) return emptySet()
|
||||
return functionsCache.getValue(classSymbol)?.keys.orEmpty()
|
||||
}
|
||||
|
||||
override fun getNestedClassifiersNames(classSymbol: FirClassSymbol<*>): Set<Name> {
|
||||
if (!classSymbol.isSuitableJavaClass()) return emptySet()
|
||||
val name = builderClassCache.getValue(classSymbol)?.name ?: return emptySet()
|
||||
return setOf(name)
|
||||
}
|
||||
|
||||
override fun generateFunctions(callableId: CallableId, context: MemberGenerationContext?): List<FirNamedFunctionSymbol> {
|
||||
val classSymbol = context?.owner ?: return emptyList()
|
||||
return functionsCache.getValue(classSymbol)?.get(callableId.callableName).orEmpty().map { it.symbol }
|
||||
}
|
||||
|
||||
override fun generateClassLikeDeclaration(classId: ClassId): FirClassLikeSymbol<*>? {
|
||||
val outerClassId = classId.outerClassId ?: return null
|
||||
val outerClassSymbol = session.symbolProvider.getClassLikeSymbolByClassId(outerClassId) as? FirClassSymbol<*> ?: return null
|
||||
if (!outerClassSymbol.isSuitableJavaClass()) return null
|
||||
return builderClassCache.getValue(outerClassSymbol)?.symbol
|
||||
}
|
||||
|
||||
private fun createFunctions(classSymbol: FirClassSymbol<*>): Map<Name, List<FirJavaMethod>>? {
|
||||
val builder = lombokService.getBuilder(classSymbol) ?: return null
|
||||
val functions = mutableListOf<FirJavaMethod>()
|
||||
val classId = classSymbol.classId
|
||||
val builderClassName = builder.builderClassName.replace("*", classId.shortClassName.asString())
|
||||
val builderClassId = classId.createNestedClassId(Name.identifier(builderClassName))
|
||||
|
||||
val builderType = builderClassId.constructClassLikeType(emptyArray(), isNullable = false)
|
||||
val visibility = builder.visibility.toVisibility()
|
||||
functions += classSymbol.createJavaMethod(
|
||||
Name.identifier(builder.builderMethodName),
|
||||
valueParameters = emptyList(),
|
||||
returnTypeRef = builderType.toFirResolvedTypeRef(),
|
||||
visibility = visibility,
|
||||
modality = Modality.FINAL,
|
||||
dispatchReceiverType = null,
|
||||
isStatic = true
|
||||
)
|
||||
|
||||
if (builder.requiresToBuilder) {
|
||||
functions += classSymbol.createJavaMethod(
|
||||
Name.identifier(TO_BUILDER),
|
||||
valueParameters = emptyList(),
|
||||
returnTypeRef = builderType.toFirResolvedTypeRef(),
|
||||
visibility = visibility,
|
||||
modality = Modality.FINAL,
|
||||
)
|
||||
}
|
||||
|
||||
return functions.groupBy { it.name }
|
||||
}
|
||||
|
||||
@OptIn(SymbolInternals::class)
|
||||
private fun createBuilderClass(classSymbol: FirClassSymbol<*>): FirJavaClass? {
|
||||
val javaClass = classSymbol.fir as? FirJavaClass ?: return null
|
||||
val builder = lombokService.getBuilder(classSymbol) ?: return null
|
||||
val builderName = Name.identifier(builder.builderClassName.replace("*", classSymbol.name.asString()))
|
||||
val visibility = builder.visibility.toVisibility()
|
||||
val builderClass = classSymbol.createJavaClass(
|
||||
session,
|
||||
builderName,
|
||||
visibility,
|
||||
Modality.FINAL,
|
||||
isStatic = true,
|
||||
superTypeRefs = listOf(session.builtinTypes.anyType)
|
||||
)?.apply {
|
||||
declarations += symbol.createDefaultJavaConstructor(visibility)
|
||||
declarations += symbol.createJavaMethod(
|
||||
Name.identifier(builder.buildMethodName),
|
||||
valueParameters = emptyList(),
|
||||
returnTypeRef = classSymbol.defaultType().toFirResolvedTypeRef(),
|
||||
visibility = visibility,
|
||||
modality = Modality.FINAL
|
||||
)
|
||||
val fields = javaClass.declarations.filterIsInstance<FirJavaField>()
|
||||
for (field in fields) {
|
||||
when (val singular = lombokService.getSingular(field.symbol)) {
|
||||
null -> createSetterMethod(builder, field, symbol, declarations)
|
||||
else -> createMethodsForSingularFields(builder, singular, field, symbol, declarations)
|
||||
}
|
||||
}
|
||||
|
||||
} ?: return null
|
||||
|
||||
|
||||
return builderClass
|
||||
}
|
||||
|
||||
private fun createSetterMethod(
|
||||
builder: Builder,
|
||||
field: FirJavaField,
|
||||
builderClassSymbol: FirRegularClassSymbol,
|
||||
destination: MutableList<FirDeclaration>
|
||||
) {
|
||||
val fieldName = field.name
|
||||
val setterName = fieldName.toMethodName(builder)
|
||||
destination += builderClassSymbol.createJavaMethod(
|
||||
name = setterName,
|
||||
valueParameters = listOf(ConeLombokValueParameter(fieldName, field.returnTypeRef)),
|
||||
returnTypeRef = builderClassSymbol.defaultType().toFirResolvedTypeRef(),
|
||||
modality = Modality.FINAL,
|
||||
visibility = builder.visibility.toVisibility()
|
||||
)
|
||||
}
|
||||
|
||||
private fun createMethodsForSingularFields(
|
||||
builder: Builder,
|
||||
singular: Singular,
|
||||
field: FirJavaField,
|
||||
builderClassSymbol: FirRegularClassSymbol,
|
||||
destination: MutableList<FirDeclaration>
|
||||
) {
|
||||
val fieldJavaTypeRef = field.returnTypeRef as? FirJavaTypeRef ?: return
|
||||
val javaClassifierType = fieldJavaTypeRef.type as? JavaClassifierType ?: return
|
||||
val typeName = (javaClassifierType.classifier as? JavaClass)?.fqName?.asString() ?: return
|
||||
|
||||
val nameInSingularForm = (singular.singularName ?: field.name.identifier.singularForm)?.let(Name::identifier) ?: return
|
||||
|
||||
val useGuava = lombokService.config.useGuava
|
||||
val supportedCollections = LombokNames.getSupportedCollectionsForSingular(useGuava)
|
||||
val supportedMaps = LombokNames.getSupportedMapsForSingular(useGuava)
|
||||
|
||||
val addMultipleParameterType: FirTypeRef
|
||||
val valueParameters: List<ConeLombokValueParameter>
|
||||
|
||||
when (typeName) {
|
||||
in supportedCollections -> {
|
||||
val parameterType = javaClassifierType.parameterType(0, singular.allowNull) ?: return
|
||||
valueParameters = listOf(
|
||||
ConeLombokValueParameter(nameInSingularForm, parameterType.toRef())
|
||||
)
|
||||
|
||||
addMultipleParameterType = DummyJavaClassType(JavaClasses.Collection, typeArguments = listOf(parameterType)).toRef()
|
||||
}
|
||||
|
||||
in supportedMaps -> {
|
||||
val keyType = javaClassifierType.parameterType(0, singular.allowNull) ?: return
|
||||
val valueType = javaClassifierType.parameterType(1, singular.allowNull) ?: return
|
||||
valueParameters = listOf(
|
||||
ConeLombokValueParameter(Name.identifier("key"), keyType.toRef()),
|
||||
ConeLombokValueParameter(Name.identifier("value"), valueType.toRef()),
|
||||
)
|
||||
|
||||
addMultipleParameterType = DummyJavaClassType(JavaClasses.Map, typeArguments = listOf(keyType, valueType)).toRef()
|
||||
}
|
||||
|
||||
else -> return
|
||||
}
|
||||
|
||||
val builderType = builderClassSymbol.defaultType().toFirResolvedTypeRef()
|
||||
val visibility = builder.visibility.toVisibility()
|
||||
|
||||
destination += builderClassSymbol.createJavaMethod(
|
||||
name = nameInSingularForm.toMethodName(builder),
|
||||
valueParameters,
|
||||
returnTypeRef = builderType,
|
||||
modality = Modality.FINAL,
|
||||
visibility = visibility
|
||||
)
|
||||
|
||||
destination += builderClassSymbol.createJavaMethod(
|
||||
name = field.name.toMethodName(builder),
|
||||
valueParameters = listOf(ConeLombokValueParameter(field.name, addMultipleParameterType)),
|
||||
returnTypeRef = builderType,
|
||||
modality = Modality.FINAL,
|
||||
visibility = visibility
|
||||
)
|
||||
|
||||
destination += builderClassSymbol.createJavaMethod(
|
||||
name = Name.identifier("clear${field.name.identifier.capitalize()}"),
|
||||
valueParameters = listOf(),
|
||||
returnTypeRef = builderType,
|
||||
modality = Modality.FINAL,
|
||||
visibility = visibility
|
||||
)
|
||||
}
|
||||
|
||||
private fun Name.toMethodName(builder: Builder): Name {
|
||||
val prefix = builder.setterPrefix
|
||||
return if (prefix.isNullOrBlank()) {
|
||||
this
|
||||
} else {
|
||||
Name.identifier("${prefix}${identifier.capitalize()}")
|
||||
}
|
||||
}
|
||||
|
||||
private val String.singularForm: String?
|
||||
get() = StringUtil.unpluralize(this)
|
||||
|
||||
private val LombokConfig.useGuava: Boolean
|
||||
get() = getBoolean("lombok.singular.useGuava") ?: false
|
||||
|
||||
private fun JavaClassifierType.parameterType(index: Int, allowNull: Boolean): JavaType? {
|
||||
val type = typeArguments.getOrNull(index) ?: return null
|
||||
return if (allowNull) type.makeNullable() else type.makeNotNullable()
|
||||
}
|
||||
}
|
||||
|
||||
fun JavaType.makeNullable(): JavaType = withAnnotations(annotations + NullabilityJavaAnnotation.Nullable)
|
||||
fun JavaType.makeNotNullable(): JavaType = withAnnotations(annotations + NullabilityJavaAnnotation.NotNull)
|
||||
|
||||
fun FirClassSymbol<*>.createJavaMethod(
|
||||
name: Name,
|
||||
valueParameters: List<ConeLombokValueParameter>,
|
||||
returnTypeRef: FirTypeRef,
|
||||
visibility: Visibility,
|
||||
modality: Modality,
|
||||
dispatchReceiverType: ConeSimpleKotlinType? = this.defaultType(),
|
||||
isStatic: Boolean = false
|
||||
): FirJavaMethod {
|
||||
return buildJavaMethod {
|
||||
moduleData = this@createJavaMethod.moduleData
|
||||
this.returnTypeRef = returnTypeRef
|
||||
this.dispatchReceiverType = dispatchReceiverType
|
||||
this.name = name
|
||||
symbol = FirNamedFunctionSymbol(CallableId(classId, name))
|
||||
status = FirResolvedDeclarationStatusImpl(visibility, modality, visibility.toEffectiveVisibility(this@createJavaMethod)).apply {
|
||||
this.isStatic = isStatic
|
||||
}
|
||||
isFromSource = true
|
||||
annotationBuilder = { emptyList() }
|
||||
for (valueParameter in valueParameters) {
|
||||
this.valueParameters += buildJavaValueParameter {
|
||||
moduleData = this@createJavaMethod.moduleData
|
||||
this.returnTypeRef = valueParameter.typeRef
|
||||
this.name = valueParameter.name
|
||||
annotationBuilder = { emptyList() }
|
||||
isVararg = false
|
||||
isFromSource = true
|
||||
}
|
||||
}
|
||||
}.apply {
|
||||
if (isStatic) {
|
||||
containingClassForStaticMemberAttr = this@createJavaMethod.toLookupTag()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun FirClassSymbol<*>.createDefaultJavaConstructor(
|
||||
visibility: Visibility,
|
||||
): FirJavaConstructor {
|
||||
val outerClassSymbol = this
|
||||
return buildJavaConstructor {
|
||||
moduleData = outerClassSymbol.moduleData
|
||||
isFromSource = true
|
||||
symbol = FirConstructorSymbol(classId)
|
||||
isInner = outerClassSymbol.rawStatus.isInner
|
||||
status = FirResolvedDeclarationStatusImpl(
|
||||
visibility,
|
||||
Modality.FINAL,
|
||||
visibility.toEffectiveVisibility(outerClassSymbol)
|
||||
).apply {
|
||||
isExpect = false
|
||||
isActual = false
|
||||
isOverride = false
|
||||
isInner = this@buildJavaConstructor.isInner
|
||||
}
|
||||
this.visibility = visibility
|
||||
isPrimary = false
|
||||
returnTypeRef = buildResolvedTypeRef {
|
||||
type = outerClassSymbol.defaultType()
|
||||
}
|
||||
dispatchReceiverType = if (isInner) outerClassSymbol.defaultType() else null
|
||||
typeParameters += outerClassSymbol.typeParameterSymbols.map { buildConstructedClassTypeParameterRef { symbol = it } }
|
||||
annotationBuilder = { emptyList() }
|
||||
}
|
||||
}
|
||||
|
||||
class ConeLombokValueParameter(val name: Name, val typeRef: FirTypeRef)
|
||||
|
||||
@OptIn(SymbolInternals::class)
|
||||
fun FirClassSymbol<*>.createJavaClass(
|
||||
session: FirSession,
|
||||
name: Name,
|
||||
visibility: Visibility,
|
||||
modality: Modality,
|
||||
isStatic: Boolean,
|
||||
superTypeRefs: List<FirTypeRef>,
|
||||
): FirJavaClass? {
|
||||
val containingClass = this.fir as? FirJavaClass ?: return null
|
||||
val classId = containingClass.classId.createNestedClassId(name)
|
||||
return buildJavaClass {
|
||||
moduleData = containingClass.moduleData
|
||||
symbol = FirRegularClassSymbol(classId)
|
||||
this.name = name
|
||||
isFromSource = true
|
||||
this.visibility = visibility
|
||||
this.modality = modality
|
||||
this.isStatic = isStatic
|
||||
classKind = ClassKind.CLASS
|
||||
javaTypeParameterStack = containingClass.javaTypeParameterStack
|
||||
scopeProvider = JavaScopeProvider
|
||||
if (!isStatic) {
|
||||
typeParameters += containingClass.typeParameters.map {
|
||||
buildOuterClassTypeParameterRef { symbol = it.symbol }
|
||||
}
|
||||
}
|
||||
this.superTypeRefs += superTypeRefs
|
||||
val effectiveVisibility = containingClass.effectiveVisibility.lowerBound(
|
||||
visibility.toEffectiveVisibility(this@createJavaClass, forClass = true),
|
||||
session.typeContext
|
||||
)
|
||||
isTopLevel = false
|
||||
status = FirResolvedDeclarationStatusImpl(
|
||||
visibility,
|
||||
modality,
|
||||
effectiveVisibility
|
||||
).apply {
|
||||
this.isInner = !isTopLevel && !this@buildJavaClass.isStatic
|
||||
isCompanion = false
|
||||
isData = false
|
||||
isInline = false
|
||||
isFun = classKind == ClassKind.INTERFACE
|
||||
}
|
||||
}
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright 2010-2022 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.k2.java
|
||||
|
||||
import org.jetbrains.kotlin.descriptors.Visibility
|
||||
import org.jetbrains.kotlin.load.java.structure.*
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
|
||||
object JavaClasses {
|
||||
val Collection = DummyJavaClass("Collection", javaUtilName("Collection"), numberOfTypeParameters = 1)
|
||||
val Map = DummyJavaClass("Map", javaUtilName("Map"), numberOfTypeParameters = 2)
|
||||
|
||||
|
||||
private fun javaUtilName(name: String): FqName {
|
||||
return FqName.fromSegments(listOf("java", "util", name))
|
||||
}
|
||||
}
|
||||
|
||||
class DummyJavaClass(name: String, override val fqName: FqName, numberOfTypeParameters: Int) : JavaClass {
|
||||
override val name: Name = Name.identifier(name)
|
||||
|
||||
override val isFromSource: Boolean
|
||||
get() = shouldNotBeCalled()
|
||||
override val annotations: Collection<JavaAnnotation>
|
||||
get() = shouldNotBeCalled()
|
||||
override val isDeprecatedInJavaDoc: Boolean
|
||||
get() = shouldNotBeCalled()
|
||||
|
||||
override fun findAnnotation(fqName: FqName): JavaAnnotation? {
|
||||
return null
|
||||
}
|
||||
|
||||
override val isAbstract: Boolean
|
||||
get() = shouldNotBeCalled()
|
||||
override val isStatic: Boolean
|
||||
get() = shouldNotBeCalled()
|
||||
override val isFinal: Boolean
|
||||
get() = shouldNotBeCalled()
|
||||
override val visibility: Visibility
|
||||
get() = shouldNotBeCalled()
|
||||
override val typeParameters: List<JavaTypeParameter> = (1..numberOfTypeParameters).map {
|
||||
DummyJavaTypeParameter(Name.identifier("T_$it"))
|
||||
}
|
||||
|
||||
override val supertypes: Collection<JavaClassifierType>
|
||||
get() = shouldNotBeCalled()
|
||||
override val innerClassNames: Collection<Name>
|
||||
get() = shouldNotBeCalled()
|
||||
|
||||
override fun findInnerClass(name: Name): JavaClass? {
|
||||
shouldNotBeCalled()
|
||||
}
|
||||
|
||||
override val outerClass: JavaClass?
|
||||
get() = null
|
||||
override val isInterface: Boolean
|
||||
get() = shouldNotBeCalled()
|
||||
override val isAnnotationType: Boolean
|
||||
get() = shouldNotBeCalled()
|
||||
override val isEnum: Boolean
|
||||
get() = shouldNotBeCalled()
|
||||
override val isRecord: Boolean
|
||||
get() = shouldNotBeCalled()
|
||||
override val isSealed: Boolean
|
||||
get() = shouldNotBeCalled()
|
||||
override val permittedTypes: Collection<JavaClassifierType>
|
||||
get() = shouldNotBeCalled()
|
||||
override val lightClassOriginKind: LightClassOriginKind?
|
||||
get() = shouldNotBeCalled()
|
||||
override val methods: Collection<JavaMethod>
|
||||
get() = shouldNotBeCalled()
|
||||
override val fields: Collection<JavaField>
|
||||
get() = shouldNotBeCalled()
|
||||
override val constructors: Collection<JavaConstructor>
|
||||
get() = shouldNotBeCalled()
|
||||
override val recordComponents: Collection<JavaRecordComponent>
|
||||
get() = shouldNotBeCalled()
|
||||
|
||||
override fun hasDefaultConstructor(): Boolean {
|
||||
shouldNotBeCalled()
|
||||
}
|
||||
}
|
||||
|
||||
class DummyJavaTypeParameter(override val name: Name) : JavaTypeParameter {
|
||||
override val isFromSource: Boolean
|
||||
get() = shouldNotBeCalled()
|
||||
override val annotations: Collection<JavaAnnotation>
|
||||
get() = shouldNotBeCalled()
|
||||
override val isDeprecatedInJavaDoc: Boolean
|
||||
get() = shouldNotBeCalled()
|
||||
|
||||
override fun findAnnotation(fqName: FqName): JavaAnnotation? {
|
||||
shouldNotBeCalled()
|
||||
}
|
||||
|
||||
override val upperBounds: Collection<JavaClassifierType>
|
||||
get() = emptyList()
|
||||
}
|
||||
|
||||
private fun shouldNotBeCalled(): Nothing = error("should not be called")
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright 2010-2022 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.k2.java
|
||||
|
||||
import org.jetbrains.kotlin.builtins.PrimitiveType
|
||||
import org.jetbrains.kotlin.fir.types.jvm.FirJavaTypeRef
|
||||
import org.jetbrains.kotlin.fir.types.jvm.buildJavaTypeRef
|
||||
import org.jetbrains.kotlin.load.java.structure.*
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.name.SpecialNames
|
||||
|
||||
abstract class WrappedJavaType<T : JavaType>(val original: T, private val ownAnnotations: Collection<JavaAnnotation>?) : JavaType {
|
||||
override val annotations: Collection<JavaAnnotation>
|
||||
get() = ownAnnotations ?: original.annotations
|
||||
|
||||
override val isDeprecatedInJavaDoc: Boolean
|
||||
get() = original.isDeprecatedInJavaDoc
|
||||
}
|
||||
|
||||
class WrappedJavaArrayType(
|
||||
original: JavaArrayType,
|
||||
ownAnnotations: Collection<JavaAnnotation>?
|
||||
) : WrappedJavaType<JavaArrayType>(original, ownAnnotations), JavaArrayType {
|
||||
override val componentType: JavaType
|
||||
get() = original.componentType
|
||||
}
|
||||
|
||||
class WrappedJavaPrimitiveType(
|
||||
original: JavaPrimitiveType,
|
||||
ownAnnotations: Collection<JavaAnnotation>?
|
||||
) : WrappedJavaType<JavaPrimitiveType>(original, ownAnnotations), JavaPrimitiveType {
|
||||
override val type: PrimitiveType?
|
||||
get() = original.type
|
||||
}
|
||||
|
||||
class WrappedJavaWildcardType(
|
||||
original: JavaWildcardType,
|
||||
ownAnnotations: Collection<JavaAnnotation>?
|
||||
) : WrappedJavaType<JavaWildcardType>(original, ownAnnotations), JavaWildcardType {
|
||||
override val bound: JavaType?
|
||||
get() = original.bound
|
||||
override val isExtends: Boolean
|
||||
get() = original.isExtends
|
||||
}
|
||||
|
||||
class WrappedJavaClassifierType(
|
||||
original: JavaClassifierType,
|
||||
ownAnnotations: Collection<JavaAnnotation>?,
|
||||
) : WrappedJavaType<JavaClassifierType>(original, ownAnnotations), JavaClassifierType {
|
||||
override val classifier: JavaClassifier?
|
||||
get() = original.classifier
|
||||
override val typeArguments: List<JavaType?>
|
||||
get() = original.typeArguments
|
||||
override val isRaw: Boolean
|
||||
get() = original.isRaw
|
||||
override val classifierQualifiedName: String
|
||||
get() = original.classifierQualifiedName
|
||||
override val presentableText: String
|
||||
get() = original.presentableText
|
||||
}
|
||||
|
||||
fun JavaType.withAnnotations(annotations: Collection<JavaAnnotation>): JavaType = when (this) {
|
||||
is JavaArrayType -> WrappedJavaArrayType(this, annotations)
|
||||
is JavaClassifierType -> WrappedJavaClassifierType(this, annotations)
|
||||
is JavaPrimitiveType -> WrappedJavaPrimitiveType(this, annotations)
|
||||
is JavaWildcardType -> WrappedJavaWildcardType(this, annotations)
|
||||
else -> this
|
||||
}
|
||||
|
||||
abstract class NullabilityJavaAnnotation(override val classId: ClassId) : JavaAnnotation {
|
||||
override val arguments: Collection<JavaAnnotationArgument>
|
||||
get() = emptyList()
|
||||
|
||||
override fun resolve(): JavaClass? = null
|
||||
|
||||
object NotNull : NullabilityJavaAnnotation(ClassId(ORG_JETBRAINS_ANNOTATIONS, Name.identifier("NotNull")))
|
||||
object Nullable : NullabilityJavaAnnotation(ClassId(ORG_JETBRAINS_ANNOTATIONS, Name.identifier("Nullable")))
|
||||
|
||||
companion object {
|
||||
private val ORG_JETBRAINS_ANNOTATIONS = FqName.fromSegments(listOf("org", "jetbrains", "annotations"))
|
||||
}
|
||||
}
|
||||
|
||||
class DummyJavaClassType(
|
||||
override val classifier: JavaClass,
|
||||
override val typeArguments: List<JavaType?>
|
||||
) : JavaClassifierType {
|
||||
override val annotations: Collection<JavaAnnotation>
|
||||
get() = emptyList()
|
||||
override val isDeprecatedInJavaDoc: Boolean
|
||||
get() = false
|
||||
override val isRaw: Boolean
|
||||
get() = false
|
||||
override val classifierQualifiedName: String
|
||||
get() = classifier.fqName?.asString() ?: SpecialNames.NO_NAME_PROVIDED.asString()
|
||||
override val presentableText: String
|
||||
get() = classifierQualifiedName
|
||||
}
|
||||
|
||||
fun JavaType.toRef(): FirJavaTypeRef = buildJavaTypeRef {
|
||||
type = this@toRef
|
||||
annotationBuilder = { emptyList() }
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
// FIR_IDENTICAL
|
||||
// FILE: User.java
|
||||
|
||||
import lombok.Builder;
|
||||
|
||||
Reference in New Issue
Block a user