xref: /aosp_15_r20/external/kotlinpoet/kotlinpoet/src/commonMain/kotlin/com/squareup/kotlinpoet/PropertySpec.kt (revision 3c321d951dd070fb96f8ba59e952ffc3131379a0)
1 /*
2  * Copyright (C) 2015 Square, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * https://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.squareup.kotlinpoet
17 
18 import com.squareup.kotlinpoet.FunSpec.Companion.GETTER
19 import com.squareup.kotlinpoet.FunSpec.Companion.SETTER
20 import com.squareup.kotlinpoet.KModifier.Target.PROPERTY
21 import java.lang.reflect.Type
22 import java.util.EnumSet
23 import javax.lang.model.element.Element
24 import kotlin.reflect.KClass
25 
26 /** A generated property declaration. */
27 @OptIn(ExperimentalKotlinPoetApi::class)
28 public class PropertySpec private constructor(
29   builder: Builder,
30   private val tagMap: TagMap = builder.buildTagMap(),
31   private val delegateOriginatingElementsHolder: OriginatingElementsHolder = builder.buildOriginatingElements(),
32   private val contextReceivers: ContextReceivers = builder.buildContextReceivers(),
33 ) : Taggable by tagMap,
34   OriginatingElementsHolder by delegateOriginatingElementsHolder,
35   ContextReceivable by contextReceivers,
36   Annotatable,
37   Documentable {
38   public val mutable: Boolean = builder.mutable
39   public val name: String = builder.name
40   public val type: TypeName = builder.type
41   override val kdoc: CodeBlock = builder.kdoc.build()
42   override val annotations: List<AnnotationSpec> = builder.annotations.toImmutableList()
43   public val modifiers: Set<KModifier> = builder.modifiers.toImmutableSet()
44   public val typeVariables: List<TypeVariableName> = builder.typeVariables.toImmutableList()
45   public val initializer: CodeBlock? = builder.initializer
46   public val delegated: Boolean = builder.delegated
47   public val getter: FunSpec? = builder.getter
48   public val setter: FunSpec? = builder.setter
49   public val receiverType: TypeName? = builder.receiverType
50 
51   init {
52     require(
<lambda>null53       typeVariables.none { it.isReified } ||
54         (getter != null || setter != null) &&
55         (getter == null || KModifier.INLINE in getter.modifiers) &&
56         (setter == null || KModifier.INLINE in setter.modifiers),
<lambda>null57     ) {
58       "only type parameters of properties with inline getters and/or setters can be reified!"
59     }
<lambda>null60     require(mutable || setter == null) {
61       "only a mutable property can have a setter"
62     }
63   }
64 
emitnull65   internal fun emit(
66     codeWriter: CodeWriter,
67     implicitModifiers: Set<KModifier>,
68     withInitializer: Boolean = true,
69     emitKdoc: Boolean = true,
70     inline: Boolean = false,
71     inlineAnnotations: Boolean = inline,
72   ) {
73     val isInlineProperty = getter?.modifiers?.contains(KModifier.INLINE) ?: false &&
74       (!mutable || setter?.modifiers?.contains(KModifier.INLINE) ?: false)
75     val propertyModifiers = if (isInlineProperty) modifiers + KModifier.INLINE else modifiers
76     if (emitKdoc) {
77       codeWriter.emitKdoc(kdoc.ensureEndsWithNewLine())
78     }
79     codeWriter.emitContextReceivers(contextReceiverTypes, suffix = "\n")
80     codeWriter.emitAnnotations(annotations, inlineAnnotations)
81     codeWriter.emitModifiers(propertyModifiers, implicitModifiers)
82     codeWriter.emitCode(if (mutable) "var·" else "val·")
83     if (typeVariables.isNotEmpty()) {
84       codeWriter.emitTypeVariables(typeVariables)
85       codeWriter.emit(" ")
86     }
87     if (receiverType != null) {
88       if (receiverType is LambdaTypeName) {
89         codeWriter.emitCode("(%T).", receiverType)
90       } else {
91         codeWriter.emitCode("%T.", receiverType)
92       }
93     }
94     codeWriter.emitCode("%N: %T", this, type)
95     if (withInitializer && initializer != null) {
96       if (delegated) {
97         codeWriter.emit(" by ")
98       } else {
99         codeWriter.emitCode(" = ")
100       }
101       val initializerFormat = if (initializer.hasStatements()) "%L" else "«%L»"
102       codeWriter.emitCode(
103         codeBlock = CodeBlock.of(initializerFormat, initializer.trimTrailingNewLine()),
104         isConstantContext = KModifier.CONST in modifiers,
105       )
106     }
107     codeWriter.emitWhereBlock(typeVariables)
108     if (!inline) codeWriter.emit("\n")
109     val implicitAccessorModifiers = EnumSet.noneOf(KModifier::class.java)
110     for (modifier in implicitModifiers) {
111       // Omit visibility modifiers, accessor visibility will default to the property's visibility.
112       if (modifier !in VISIBILITY_MODIFIERS) {
113         implicitAccessorModifiers.add(modifier)
114       }
115     }
116     if (isInlineProperty) {
117       implicitAccessorModifiers.add(KModifier.INLINE)
118     }
119     if (getter != null) {
120       codeWriter.emitCode("⇥")
121       getter.emit(codeWriter, null, implicitAccessorModifiers, false)
122       codeWriter.emitCode("⇤")
123     }
124     if (setter != null) {
125       codeWriter.emitCode("⇥")
126       setter.emit(codeWriter, null, implicitAccessorModifiers, false)
127       codeWriter.emitCode("⇤")
128     }
129   }
130 
fromPrimaryConstructorParameternull131   internal fun fromPrimaryConstructorParameter(parameter: ParameterSpec): PropertySpec {
132     val builder = toBuilder()
133       .addAnnotations(parameter.annotations)
134     builder.isPrimaryConstructorParameter = true
135     builder.modifiers += parameter.modifiers
136     if (builder.kdoc.isEmpty()) {
137       builder.addKdoc(parameter.kdoc)
138     }
139     return builder.build()
140   }
141 
equalsnull142   override fun equals(other: Any?): Boolean {
143     if (this === other) return true
144     if (other == null) return false
145     if (javaClass != other.javaClass) return false
146     return toString() == other.toString()
147   }
148 
hashCodenull149   override fun hashCode(): Int = toString().hashCode()
150 
151   override fun toString(): String = buildCodeString { emit(this, emptySet()) }
152 
153   @JvmOverloads
toBuildernull154   public fun toBuilder(name: String = this.name, type: TypeName = this.type): Builder {
155     val builder = Builder(name, type)
156     builder.mutable = mutable
157     builder.kdoc.add(kdoc)
158     builder.annotations += annotations
159     builder.modifiers += modifiers
160     builder.typeVariables += typeVariables
161     builder.initializer = initializer
162     builder.delegated = delegated
163     builder.setter = setter
164     builder.getter = getter
165     builder.receiverType = receiverType
166     builder.tags += tagMap.tags
167     builder.originatingElements += originatingElements
168     builder.contextReceiverTypes += contextReceiverTypes
169     return builder
170   }
171 
172   public class Builder internal constructor(
173     internal val name: String,
174     internal val type: TypeName,
175   ) : Taggable.Builder<Builder>,
176     OriginatingElementsHolder.Builder<Builder>,
177     ContextReceivable.Builder<Builder>,
178     Annotatable.Builder<Builder>,
179     Documentable.Builder<Builder> {
180     internal var isPrimaryConstructorParameter = false
181     internal var mutable = false
182     internal var initializer: CodeBlock? = null
183     internal var delegated = false
184     internal var getter: FunSpec? = null
185     internal var setter: FunSpec? = null
186     internal var receiverType: TypeName? = null
187 
188     public val modifiers: MutableList<KModifier> = mutableListOf()
189     public val typeVariables: MutableList<TypeVariableName> = mutableListOf()
190     override val tags: MutableMap<KClass<*>, Any> = mutableMapOf()
191     override val kdoc: CodeBlock.Builder = CodeBlock.builder()
192     override val originatingElements: MutableList<Element> = mutableListOf()
193     override val annotations: MutableList<AnnotationSpec> = mutableListOf()
194 
195     @ExperimentalKotlinPoetApi
196     override val contextReceiverTypes: MutableList<TypeName> = mutableListOf()
197 
198     /** True to create a `var` instead of a `val`. */
<lambda>null199     public fun mutable(mutable: Boolean = true): Builder = apply {
200       this.mutable = mutable
201     }
202 
<lambda>null203     public fun addModifiers(vararg modifiers: KModifier): Builder = apply {
204       this.modifiers += modifiers
205     }
206 
<lambda>null207     public fun addModifiers(modifiers: Iterable<KModifier>): Builder = apply {
208       this.modifiers += modifiers
209     }
210 
<lambda>null211     public fun addTypeVariables(typeVariables: Iterable<TypeVariableName>): Builder = apply {
212       this.typeVariables += typeVariables
213     }
214 
addTypeVariablenull215     public fun addTypeVariable(typeVariable: TypeVariableName): Builder = apply {
216       typeVariables += typeVariable
217     }
218 
initializernull219     public fun initializer(format: String, vararg args: Any?): Builder =
220       initializer(CodeBlock.of(format, *args))
221 
222     public fun initializer(codeBlock: CodeBlock?): Builder = apply {
223       this.initializer = codeBlock
224       this.delegated = false
225     }
226 
delegatenull227     public fun delegate(format: String, vararg args: Any?): Builder =
228       delegate(CodeBlock.of(format, *args))
229 
230     public fun delegate(codeBlock: CodeBlock): Builder = apply {
231       this.initializer = codeBlock
232       this.delegated = true
233     }
234 
<lambda>null235     public fun getter(getter: FunSpec?): Builder = apply {
236       require(getter == null || getter.name == GETTER) { "${getter!!.name} is not a getter" }
237       this.getter = getter
238     }
239 
<lambda>null240     public fun setter(setter: FunSpec?): Builder = apply {
241       require(setter == null || setter.name == SETTER) { "${setter!!.name} is not a setter" }
242       this.setter = setter
243     }
244 
<lambda>null245     public fun receiver(receiverType: TypeName?): Builder = apply {
246       this.receiverType = receiverType
247     }
248 
249     @DelicateKotlinPoetApi(
250       message = "Java reflection APIs don't give complete information on Kotlin types. Consider " +
251         "using the kotlinpoet-metadata APIs instead.",
252     )
receivernull253     public fun receiver(receiverType: Type): Builder = receiver(receiverType.asTypeName())
254 
255     public fun receiver(receiverType: KClass<*>): Builder = receiver(receiverType.asTypeName())
256 
257     //region Overrides for binary compatibility
258     @Suppress("RedundantOverride")
259     override fun addAnnotation(annotationSpec: AnnotationSpec): Builder = super.addAnnotation(annotationSpec)
260 
261     @Suppress("RedundantOverride")
262     override fun addAnnotations(annotationSpecs: Iterable<AnnotationSpec>): Builder =
263       super.addAnnotations(annotationSpecs)
264 
265     @Suppress("RedundantOverride")
266     override fun addAnnotation(annotation: ClassName): Builder = super.addAnnotation(annotation)
267 
268     @DelicateKotlinPoetApi(
269       message = "Java reflection APIs don't give complete information on Kotlin types. Consider " +
270         "using the kotlinpoet-metadata APIs instead.",
271     )
272     override fun addAnnotation(annotation: Class<*>): Builder = super.addAnnotation(annotation)
273 
274     @Suppress("RedundantOverride")
275     override fun addAnnotation(annotation: KClass<*>): Builder = super.addAnnotation(annotation)
276 
277     @Suppress("RedundantOverride")
278     override fun addKdoc(format: String, vararg args: Any): Builder = super.addKdoc(format, *args)
279 
280     @Suppress("RedundantOverride")
281     override fun addKdoc(block: CodeBlock): Builder = super.addKdoc(block)
282     //endregion
283 
284     public fun build(): PropertySpec {
285       if (KModifier.INLINE in modifiers) {
286         throw IllegalArgumentException(
287           "KotlinPoet doesn't allow setting the inline modifier on " +
288             "properties. You should mark either the getter, the setter, or both inline.",
289         )
290       }
291       for (it in modifiers) {
292         if (!isPrimaryConstructorParameter) it.checkTarget(PROPERTY)
293       }
294       return PropertySpec(this)
295     }
296   }
297 
298   public companion object {
buildernull299     @JvmStatic public fun builder(
300       name: String,
301       type: TypeName,
302       vararg modifiers: KModifier,
303     ): Builder {
304       return Builder(name, type).addModifiers(*modifiers)
305     }
306 
buildernull307     @JvmStatic public fun builder(name: String, type: Type, vararg modifiers: KModifier): Builder =
308       builder(name, type.asTypeName(), *modifiers)
309 
310     @JvmStatic public fun builder(
311       name: String,
312       type: KClass<*>,
313       vararg modifiers: KModifier,
314     ): Builder = builder(name, type.asTypeName(), *modifiers)
315 
316     @JvmStatic public fun builder(
317       name: String,
318       type: TypeName,
319       modifiers: Iterable<KModifier>,
320     ): Builder {
321       return Builder(name, type).addModifiers(modifiers)
322     }
323 
324     @DelicateKotlinPoetApi(
325       message = "Java reflection APIs don't give complete information on Kotlin types. Consider " +
326         "using the kotlinpoet-metadata APIs instead.",
327     )
328     @JvmStatic
buildernull329     public fun builder(
330       name: String,
331       type: Type,
332       modifiers: Iterable<KModifier>,
333     ): Builder = builder(name, type.asTypeName(), modifiers)
334 
335     @JvmStatic public fun builder(
336       name: String,
337       type: KClass<*>,
338       modifiers: Iterable<KModifier>,
339     ): Builder = builder(name, type.asTypeName(), modifiers)
340   }
341 }
342