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