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