xref: /aosp_15_r20/external/kotlinpoet/kotlinpoet/src/commonMain/kotlin/com/squareup/kotlinpoet/AnnotationSpec.kt (revision 3c321d951dd070fb96f8ba59e952ffc3131379a0)
1 /*
<lambda>null2  * 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 java.lang.reflect.Array
19 import java.util.Objects
20 import javax.lang.model.element.AnnotationMirror
21 import javax.lang.model.element.AnnotationValue
22 import javax.lang.model.element.TypeElement
23 import javax.lang.model.element.VariableElement
24 import javax.lang.model.type.TypeMirror
25 import javax.lang.model.util.SimpleAnnotationValueVisitor8
26 import kotlin.LazyThreadSafetyMode.NONE
27 import kotlin.reflect.KClass
28 
29 /** A generated annotation on a declaration. */
30 public class AnnotationSpec private constructor(
31   builder: Builder,
32   private val tagMap: TagMap = builder.buildTagMap(),
33 ) : Taggable by tagMap {
34   @Deprecated(
35     message = "Use typeName instead. This property will be removed in KotlinPoet 2.0.",
36     replaceWith = ReplaceWith("typeName"),
37   )
38   public val className: ClassName
39     get() = typeName as? ClassName ?: error("ClassName is not available. Call typeName instead.")
40   public val typeName: TypeName = builder.typeName
41   public val members: List<CodeBlock> = builder.members.toImmutableList()
42   public val useSiteTarget: UseSiteTarget? = builder.useSiteTarget
43 
44   /** Lazily-initialized toString of this AnnotationSpec.  */
45   private val cachedString by lazy(NONE) {
46     buildCodeString {
47       emit(this, inline = true, asParameter = false)
48     }
49   }
50 
51   internal fun emit(codeWriter: CodeWriter, inline: Boolean, asParameter: Boolean = false) {
52     if (!asParameter) {
53       codeWriter.emit("@")
54     }
55     if (useSiteTarget != null) {
56       codeWriter.emit(useSiteTarget.keyword + ":")
57     }
58     codeWriter.emitCode("%T", typeName)
59 
60     if (members.isEmpty() && !asParameter) {
61       // @Singleton
62       return
63     }
64 
65     val whitespace = if (inline) "" else "\n"
66     val memberSeparator = if (inline) ", " else ",\n"
67     val memberSuffix = if (!inline && members.size > 1) "," else ""
68 
69     // Inline:
70     //   @Column(name = "updated_at", nullable = false)
71     //
72     // Not inline:
73     //   @Column(
74     //       name = "updated_at",
75     //       nullable = false,
76     //   )
77 
78     codeWriter.emit("(")
79     if (members.size > 1) codeWriter.emit(whitespace).indent(1)
80     codeWriter.emitCode(
81       codeBlock = members
82         .map { if (inline) it.replaceAll("[⇥|⇤]", "") else it }
83         .joinToCode(separator = memberSeparator, suffix = memberSuffix),
84       isConstantContext = true,
85     )
86     if (members.size > 1) codeWriter.unindent(1).emit(whitespace)
87     codeWriter.emit(")")
88   }
89 
90   public fun toBuilder(): Builder {
91     val builder = Builder(typeName)
92     builder.members += members
93     builder.useSiteTarget = useSiteTarget
94     builder.tags += tagMap.tags
95     return builder
96   }
97 
98   override fun equals(other: Any?): Boolean {
99     if (this === other) return true
100     if (other == null) return false
101     if (javaClass != other.javaClass) return false
102     return toString() == other.toString()
103   }
104 
105   override fun hashCode(): Int = toString().hashCode()
106 
107   override fun toString(): String = cachedString
108 
109   public enum class UseSiteTarget(internal val keyword: String) {
110     FILE("file"),
111     PROPERTY("property"),
112     FIELD("field"),
113     GET("get"),
114     SET("set"),
115     RECEIVER("receiver"),
116     PARAM("param"),
117     SETPARAM("setparam"),
118     DELEGATE("delegate"),
119   }
120 
121   public class Builder internal constructor(
122     internal val typeName: TypeName,
123   ) : Taggable.Builder<Builder> {
124     internal var useSiteTarget: UseSiteTarget? = null
125 
126     public val members: MutableList<CodeBlock> = mutableListOf()
127     override val tags: MutableMap<KClass<*>, Any> = mutableMapOf()
128 
129     public fun addMember(format: String, vararg args: Any): Builder =
130       addMember(CodeBlock.of(format, *args))
131 
132     public fun addMember(codeBlock: CodeBlock): Builder = apply {
133       members += codeBlock
134     }
135 
136     public fun useSiteTarget(useSiteTarget: UseSiteTarget?): Builder = apply {
137       this.useSiteTarget = useSiteTarget
138     }
139 
140     public fun build(): AnnotationSpec = AnnotationSpec(this)
141 
142     public companion object {
143       /**
144        * Creates a [CodeBlock] with parameter `format` depending on the given `value` object.
145        * Handles a number of special cases, such as appending "f" to `Float` values, and uses
146        * `%L` for other types.
147        */
148       internal fun memberForValue(value: Any) = when (value) {
149         is Class<*> -> CodeBlock.of("%T::class", value)
150         is Enum<*> -> CodeBlock.of("%T.%L", value.javaClass, value.name)
151         is String -> CodeBlock.of("%S", value)
152         is Float -> CodeBlock.of("%Lf", value)
153         is Char -> CodeBlock.of("'%L'", characterLiteralWithoutSingleQuotes(value))
154         else -> CodeBlock.of("%L", value)
155       }
156     }
157   }
158 
159   /**
160    * Annotation value visitor adding members to the given builder instance.
161    */
162   @OptIn(DelicateKotlinPoetApi::class)
163   private class Visitor(
164     val builder: CodeBlock.Builder,
165   ) : SimpleAnnotationValueVisitor8<CodeBlock.Builder, String>(builder) {
166 
167     override fun defaultAction(o: Any, name: String) =
168       builder.add(Builder.memberForValue(o))
169 
170     override fun visitAnnotation(a: AnnotationMirror, name: String) =
171       builder.add("%L", get(a))
172 
173     override fun visitEnumConstant(c: VariableElement, name: String) =
174       builder.add("%T.%L", c.asType().asTypeName(), c.simpleName)
175 
176     override fun visitType(t: TypeMirror, name: String) =
177       builder.add("%T::class", t.asTypeName())
178 
179     override fun visitArray(values: List<AnnotationValue>, name: String): CodeBlock.Builder {
180       builder.add("arrayOf(⇥⇥")
181       values.forEachIndexed { index, value ->
182         if (index > 0) builder.add(", ")
183         value.accept(this, name)
184       }
185       builder.add("⇤⇤)")
186       return builder
187     }
188   }
189 
190   public companion object {
191     @DelicateKotlinPoetApi(
192       message = "Java reflection APIs don't give complete information on Kotlin types. Consider " +
193         "using the kotlinpoet-metadata APIs instead.",
194     )
195     @JvmStatic
196     @JvmOverloads
197     public fun get(
198       annotation: Annotation,
199       includeDefaultValues: Boolean = false,
200     ): AnnotationSpec {
201       try {
202         @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
203         val javaAnnotation = annotation as java.lang.annotation.Annotation
204         val builder = builder(javaAnnotation.annotationType())
205           .tag(annotation)
206         val methods = annotation.annotationType().declaredMethods.sortedBy { it.name }
207         for (method in methods) {
208           val value = method.invoke(annotation)
209           if (!includeDefaultValues) {
210             if (Objects.deepEquals(value, method.defaultValue)) {
211               continue
212             }
213           }
214           val member = CodeBlock.builder()
215           member.add("%L = ", method.name)
216           if (value.javaClass.isArray) {
217             member.add("arrayOf(⇥⇥")
218             for (i in 0..<Array.getLength(value)) {
219               if (i > 0) member.add(", ")
220               member.add(Builder.memberForValue(Array.get(value, i)))
221             }
222             member.add("⇤⇤)")
223             builder.addMember(member.build())
224             continue
225           }
226           if (value is Annotation) {
227             member.add("%L", get(value))
228             builder.addMember(member.build())
229             continue
230           }
231           member.add("%L", Builder.memberForValue(value))
232           builder.addMember(member.build())
233         }
234         return builder.build()
235       } catch (e: Exception) {
236         throw RuntimeException("Reflecting $annotation failed!", e)
237       }
238     }
239 
240     @DelicateKotlinPoetApi(
241       message = "Mirror APIs don't give complete information on Kotlin types. Consider using" +
242         " the kotlinpoet-metadata APIs instead.",
243     )
244     @JvmStatic
245     public fun get(annotation: AnnotationMirror): AnnotationSpec {
246       val element = annotation.annotationType.asElement() as TypeElement
247       val builder = builder(element.asClassName()).tag(annotation)
248       for (executableElement in annotation.elementValues.keys) {
249         val member = CodeBlock.builder()
250         val visitor = Visitor(member)
251         val name = executableElement.simpleName.toString()
252         member.add("%L = ", name)
253         val value = annotation.elementValues[executableElement]!!
254         value.accept(visitor, name)
255         builder.addMember(member.build())
256       }
257       return builder.build()
258     }
259 
260     @JvmStatic public fun builder(type: ClassName): Builder = Builder(type)
261 
262     @JvmStatic public fun builder(type: ParameterizedTypeName): Builder = Builder(type)
263 
264     @DelicateKotlinPoetApi(
265       message = "Java reflection APIs don't give complete information on Kotlin types. Consider " +
266         "using the kotlinpoet-metadata APIs instead.",
267     )
268     @JvmStatic
269     public fun builder(type: Class<out Annotation>): Builder =
270       builder(type.asClassName())
271 
272     @JvmStatic public fun builder(type: KClass<out Annotation>): Builder =
273       builder(type.asClassName())
274   }
275 }
276