• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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(),
<lambda>null33 ) : Taggable by tagMap, OriginatingElementsHolder by delegateOriginatingElementsHolder, ContextReceivable by contextReceivers {
34   public val mutable: Boolean = builder.mutable
35   public val name: String = builder.name
36   public val type: TypeName = builder.type
37   public val kdoc: CodeBlock = builder.kdoc.build()
38   public val annotations: List<AnnotationSpec> = builder.annotations.toImmutableList()
39   public val modifiers: Set<KModifier> = builder.modifiers.toImmutableSet()
40   public val typeVariables: List<TypeVariableName> = builder.typeVariables.toImmutableList()
41   public val initializer: CodeBlock? = builder.initializer
42   public val delegated: Boolean = builder.delegated
43   public val getter: FunSpec? = builder.getter
44   public val setter: FunSpec? = builder.setter
45   public val receiverType: TypeName? = builder.receiverType
46 
47   init {
48     require(
49       typeVariables.none { it.isReified } ||
50         (getter != null || setter != null) &&
51         (getter == null || KModifier.INLINE in getter.modifiers) &&
52         (setter == null || KModifier.INLINE in setter.modifiers),
53     ) {
54       "only type parameters of properties with inline getters and/or setters can be reified!"
55     }
56     require(mutable || setter == null) {
57       "only a mutable property can have a setter"
58     }
59     if (contextReceiverTypes.isNotEmpty()) {
60       requireNotNull(getter) { "properties with context receivers require a $GETTER" }
61       if (mutable) {
62         requireNotNull(setter) { "mutable properties with context receivers require a $SETTER" }
63       }
64     }
65   }
66 
67   internal fun emit(
68     codeWriter: CodeWriter,
69     implicitModifiers: Set<KModifier>,
70     withInitializer: Boolean = true,
71     emitKdoc: Boolean = true,
72     inline: Boolean = false,
73     inlineAnnotations: Boolean = inline,
74   ) {
75     val isInlineProperty = getter?.modifiers?.contains(KModifier.INLINE) ?: false &&
76       (!mutable || setter?.modifiers?.contains(KModifier.INLINE) ?: false)
77     val propertyModifiers = if (isInlineProperty) modifiers + KModifier.INLINE else modifiers
78     if (emitKdoc) {
79       codeWriter.emitKdoc(kdoc.ensureEndsWithNewLine())
80     }
81     codeWriter.emitContextReceivers(contextReceiverTypes, suffix = "\n")
82     codeWriter.emitAnnotations(annotations, inlineAnnotations)
83     codeWriter.emitModifiers(propertyModifiers, implicitModifiers)
84     codeWriter.emitCode(if (mutable) "var·" else "val·")
85     if (typeVariables.isNotEmpty()) {
86       codeWriter.emitTypeVariables(typeVariables)
87       codeWriter.emit(" ")
88     }
89     if (receiverType != null) {
90       if (receiverType is LambdaTypeName) {
91         codeWriter.emitCode("(%T).", receiverType)
92       } else {
93         codeWriter.emitCode("%T.", receiverType)
94       }
95     }
96     codeWriter.emitCode("%N: %T", this, type)
97     if (withInitializer && initializer != null) {
98       if (delegated) {
99         codeWriter.emit(" by ")
100       } else {
101         codeWriter.emitCode(" = ")
102       }
103       val initializerFormat = if (initializer.hasStatements()) "%L" else "«%L»"
104       codeWriter.emitCode(
105         codeBlock = CodeBlock.of(initializerFormat, initializer),
106         isConstantContext = KModifier.CONST in modifiers,
107       )
108     }
109     codeWriter.emitWhereBlock(typeVariables)
110     if (!inline) codeWriter.emit("\n")
111     val implicitAccessorModifiers = EnumSet.noneOf(KModifier::class.java)
112     for (modifier in implicitModifiers) {
113       // Omit visibility modifiers, accessor visibility will default to the property's visibility.
114       if (modifier !in VISIBILITY_MODIFIERS) {
115         implicitAccessorModifiers.add(modifier)
116       }
117     }
118     if (isInlineProperty) {
119       implicitAccessorModifiers.add(KModifier.INLINE)
120     }
121     if (getter != null) {
122       codeWriter.emitCode("⇥")
123       getter.emit(codeWriter, null, implicitAccessorModifiers, false)
124       codeWriter.emitCode("⇤")
125     }
126     if (setter != null) {
127       codeWriter.emitCode("⇥")
128       setter.emit(codeWriter, null, implicitAccessorModifiers, false)
129       codeWriter.emitCode("⇤")
130     }
131   }
132 
133   internal fun fromPrimaryConstructorParameter(parameter: ParameterSpec): PropertySpec {
134     val builder = toBuilder()
135       .addAnnotations(parameter.annotations)
136     builder.isPrimaryConstructorParameter = true
137     builder.modifiers += parameter.modifiers
138     if (builder.kdoc.isEmpty()) {
139       builder.addKdoc(parameter.kdoc)
140     }
141     return builder.build()
142   }
143 
144   override fun equals(other: Any?): Boolean {
145     if (this === other) return true
146     if (other == null) return false
147     if (javaClass != other.javaClass) return false
148     return toString() == other.toString()
149   }
150 
151   override fun hashCode(): Int = toString().hashCode()
152 
153   override fun toString(): String = buildCodeString { emit(this, emptySet()) }
154 
155   @JvmOverloads
156   public fun toBuilder(name: String = this.name, type: TypeName = this.type): Builder {
157     val builder = Builder(name, type)
158     builder.mutable = mutable
159     builder.kdoc.add(kdoc)
160     builder.annotations += annotations
161     builder.modifiers += modifiers
162     builder.typeVariables += typeVariables
163     builder.initializer = initializer
164     builder.delegated = delegated
165     builder.setter = setter
166     builder.getter = getter
167     builder.receiverType = receiverType
168     builder.tags += tagMap.tags
169     builder.originatingElements += originatingElements
170     builder.contextReceiverTypes += contextReceiverTypes
171     return builder
172   }
173 
174   public class Builder internal constructor(
175     internal val name: String,
176     internal val type: TypeName,
177   ) : Taggable.Builder<Builder>,
178     OriginatingElementsHolder.Builder<Builder>,
179     ContextReceivable.Builder<Builder> {
180     internal var isPrimaryConstructorParameter = false
181     internal var mutable = false
182     internal val kdoc = CodeBlock.builder()
183     internal var initializer: CodeBlock? = null
184     internal var delegated = false
185     internal var getter: FunSpec? = null
186     internal var setter: FunSpec? = null
187     internal var receiverType: TypeName? = null
188 
189     public val annotations: MutableList<AnnotationSpec> = mutableListOf()
190     public val modifiers: MutableList<KModifier> = mutableListOf()
191     public val typeVariables: MutableList<TypeVariableName> = mutableListOf()
192     override val tags: MutableMap<KClass<*>, Any> = mutableMapOf()
193     override val originatingElements: MutableList<Element> = mutableListOf()
194     override val contextReceiverTypes: MutableList<TypeName> = mutableListOf()
195 
196     /** True to create a `var` instead of a `val`. */
197     public fun mutable(mutable: Boolean = true): Builder = apply {
198       this.mutable = mutable
199     }
200 
201     public fun addKdoc(format: String, vararg args: Any): Builder = apply {
202       kdoc.add(format, *args)
203     }
204 
205     public fun addKdoc(block: CodeBlock): Builder = apply {
206       kdoc.add(block)
207     }
208 
209     public fun addAnnotations(annotationSpecs: Iterable<AnnotationSpec>): Builder = apply {
210       annotations += annotationSpecs
211     }
212 
213     public fun addAnnotation(annotationSpec: AnnotationSpec): Builder = apply {
214       annotations += annotationSpec
215     }
216 
217     public fun addAnnotation(annotation: ClassName): Builder = apply {
218       annotations += AnnotationSpec.builder(annotation).build()
219     }
220 
221     @DelicateKotlinPoetApi(
222       message = "Java reflection APIs don't give complete information on Kotlin types. Consider " +
223         "using the kotlinpoet-metadata APIs instead.",
224     )
225     public fun addAnnotation(annotation: Class<*>): Builder =
226       addAnnotation(annotation.asClassName())
227 
228     public fun addAnnotation(annotation: KClass<*>): Builder =
229       addAnnotation(annotation.asClassName())
230 
231     public fun addModifiers(vararg modifiers: KModifier): Builder = apply {
232       this.modifiers += modifiers
233     }
234 
235     public fun addModifiers(modifiers: Iterable<KModifier>): Builder = apply {
236       this.modifiers += modifiers
237     }
238 
239     public fun addTypeVariables(typeVariables: Iterable<TypeVariableName>): Builder = apply {
240       this.typeVariables += typeVariables
241     }
242 
243     public fun addTypeVariable(typeVariable: TypeVariableName): Builder = apply {
244       typeVariables += typeVariable
245     }
246 
247     public fun initializer(format: String, vararg args: Any?): Builder =
248       initializer(CodeBlock.of(format, *args))
249 
250     public fun initializer(codeBlock: CodeBlock?): Builder = apply {
251       this.initializer = codeBlock
252       this.delegated = false
253     }
254 
255     public fun delegate(format: String, vararg args: Any?): Builder =
256       delegate(CodeBlock.of(format, *args))
257 
258     public fun delegate(codeBlock: CodeBlock): Builder = apply {
259       this.initializer = codeBlock
260       this.delegated = true
261     }
262 
263     public fun getter(getter: FunSpec?): Builder = apply {
264       require(getter == null || getter.name == GETTER) { "${getter!!.name} is not a getter" }
265       this.getter = getter
266     }
267 
268     public fun setter(setter: FunSpec?): Builder = apply {
269       require(setter == null || setter.name == SETTER) { "${setter!!.name} is not a setter" }
270       this.setter = setter
271     }
272 
273     public fun receiver(receiverType: TypeName?): Builder = apply {
274       this.receiverType = receiverType
275     }
276 
277     @DelicateKotlinPoetApi(
278       message = "Java reflection APIs don't give complete information on Kotlin types. Consider " +
279         "using the kotlinpoet-metadata APIs instead.",
280     )
281     public fun receiver(receiverType: Type): Builder = receiver(receiverType.asTypeName())
282 
283     public fun receiver(receiverType: KClass<*>): Builder = receiver(receiverType.asTypeName())
284 
285     public fun build(): PropertySpec {
286       if (KModifier.INLINE in modifiers) {
287         throw IllegalArgumentException(
288           "KotlinPoet doesn't allow setting the inline modifier on " +
289             "properties. You should mark either the getter, the setter, or both inline.",
290         )
291       }
292       for (it in modifiers) {
293         if (!isPrimaryConstructorParameter) it.checkTarget(PROPERTY)
294       }
295       return PropertySpec(this)
296     }
297   }
298 
299   public companion object {
300     @JvmStatic public fun builder(
301       name: String,
302       type: TypeName,
303       vararg modifiers: KModifier,
304     ): Builder {
305       return Builder(name, type).addModifiers(*modifiers)
306     }
307 
308     @JvmStatic public fun builder(name: String, type: Type, vararg modifiers: KModifier): Builder =
309       builder(name, type.asTypeName(), *modifiers)
310 
311     @JvmStatic public fun builder(
312       name: String,
313       type: KClass<*>,
314       vararg modifiers: KModifier,
315     ): Builder = builder(name, type.asTypeName(), *modifiers)
316 
317     @JvmStatic public fun builder(
318       name: String,
319       type: TypeName,
320       modifiers: Iterable<KModifier>,
321     ): Builder {
322       return Builder(name, type).addModifiers(modifiers)
323     }
324 
325     @DelicateKotlinPoetApi(
326       message = "Java reflection APIs don't give complete information on Kotlin types. Consider " +
327         "using the kotlinpoet-metadata APIs instead.",
328     )
329     @JvmStatic
330     public fun builder(
331       name: String,
332       type: Type,
333       modifiers: Iterable<KModifier>,
334     ): Builder = builder(name, type.asTypeName(), modifiers)
335 
336     @JvmStatic public fun builder(
337       name: String,
338       type: KClass<*>,
339       modifiers: Iterable<KModifier>,
340     ): Builder = builder(name, type.asTypeName(), modifiers)
341   }
342 }
343