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