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