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