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 com.squareup.kotlinpoet.KModifier.CROSSINLINE
19 import com.squareup.kotlinpoet.KModifier.NOINLINE
20 import com.squareup.kotlinpoet.KModifier.VARARG
21 import java.lang.reflect.Type
22 import javax.lang.model.element.ExecutableElement
23 import javax.lang.model.element.Modifier
24 import javax.lang.model.element.VariableElement
25 import kotlin.DeprecationLevel.ERROR
26 import kotlin.reflect.KClass
27
28 /** A generated parameter declaration. */
29 public class ParameterSpec private constructor(
30 builder: Builder,
31 private val tagMap: TagMap = builder.buildTagMap(),
32 ) : Taggable by tagMap {
33 public val name: String = builder.name
34 public val kdoc: CodeBlock = builder.kdoc.build()
35 public val annotations: List<AnnotationSpec> = builder.annotations.toImmutableList()
36 public val modifiers: Set<KModifier> = builder.modifiers
37 .also {
38 LinkedHashSet(it).apply {
39 removeAll(ALLOWED_PARAMETER_MODIFIERS)
40 if (!isEmpty()) {
41 throw IllegalArgumentException("Modifiers $this are not allowed on Kotlin parameters. Allowed modifiers: $ALLOWED_PARAMETER_MODIFIERS")
42 }
43 }
44 }
45 .toImmutableSet()
46 public val type: TypeName = builder.type
47 public val defaultValue: CodeBlock? = builder.defaultValue
48
49 public constructor(name: String, type: TypeName, vararg modifiers: KModifier) :
50 this(builder(name, type, *modifiers))
51 public constructor(name: String, type: TypeName, modifiers: Iterable<KModifier>) :
52 this(builder(name, type, modifiers))
53
54 internal fun emit(
55 codeWriter: CodeWriter,
56 includeType: Boolean = true,
57 emitKdoc: Boolean = false,
58 inlineAnnotations: Boolean = true,
59 ) {
60 if (emitKdoc) codeWriter.emitKdoc(kdoc.ensureEndsWithNewLine())
61 codeWriter.emitAnnotations(annotations, inlineAnnotations)
62 codeWriter.emitModifiers(modifiers)
63 if (name.isNotEmpty()) codeWriter.emitCode("%N", this)
64 if (name.isNotEmpty() && includeType) codeWriter.emitCode(":·")
65 if (includeType) codeWriter.emitCode("%T", type)
66 emitDefaultValue(codeWriter)
67 }
68
69 internal fun emitDefaultValue(codeWriter: CodeWriter) {
70 if (defaultValue != null) {
71 codeWriter.emitCode(if (defaultValue.hasStatements()) " = %L" else " = «%L»", defaultValue)
72 }
73 }
74
75 override fun equals(other: Any?): Boolean {
76 if (this === other) return true
77 if (other == null) return false
78 if (javaClass != other.javaClass) return false
79 return toString() == other.toString()
80 }
81
82 override fun hashCode(): Int = toString().hashCode()
83
84 override fun toString(): String = buildCodeString { emit(this) }
85
86 public fun toBuilder(name: String = this.name, type: TypeName = this.type): Builder {
87 val builder = Builder(name, type)
88 builder.kdoc.add(kdoc)
89 builder.annotations += annotations
90 builder.modifiers += modifiers
91 builder.defaultValue = defaultValue
92 builder.tags += tagMap.tags
93 return builder
94 }
95
96 public class Builder internal constructor(
97 internal val name: String,
98 internal val type: TypeName,
99 ) : Taggable.Builder<Builder> {
100 internal var defaultValue: CodeBlock? = null
101
102 public val kdoc: CodeBlock.Builder = CodeBlock.builder()
103 public val annotations: MutableList<AnnotationSpec> = mutableListOf()
104 public val modifiers: MutableList<KModifier> = mutableListOf()
105 override val tags: MutableMap<KClass<*>, Any> = mutableMapOf()
106
107 public fun addKdoc(format: String, vararg args: Any): Builder = apply {
108 kdoc.add(format, *args)
109 }
110
111 public fun addKdoc(block: CodeBlock): Builder = apply {
112 kdoc.add(block)
113 }
114
115 public fun addAnnotations(annotationSpecs: Iterable<AnnotationSpec>): Builder = apply {
116 annotations += annotationSpecs
117 }
118
119 public fun addAnnotation(annotationSpec: AnnotationSpec): Builder = apply {
120 annotations += annotationSpec
121 }
122
123 public fun addAnnotation(annotation: ClassName): Builder = apply {
124 annotations += AnnotationSpec.builder(annotation).build()
125 }
126
127 public fun addAnnotation(annotation: Class<*>): Builder =
128 addAnnotation(annotation.asClassName())
129
130 public fun addAnnotation(annotation: KClass<*>): Builder =
131 addAnnotation(annotation.asClassName())
132
133 public fun addModifiers(vararg modifiers: KModifier): Builder = apply {
134 this.modifiers += modifiers
135 }
136
137 public fun addModifiers(modifiers: Iterable<KModifier>): Builder = apply {
138 this.modifiers += modifiers
139 }
140
141 @Deprecated(
142 "There are no jvm modifiers applicable to parameters in Kotlin",
143 ReplaceWith(""),
144 level = ERROR,
145 )
146 public fun jvmModifiers(modifiers: Iterable<Modifier>): Builder = apply {
147 throw IllegalArgumentException("JVM modifiers are not permitted on parameters in Kotlin")
148 }
149
150 public fun defaultValue(format: String, vararg args: Any?): Builder =
151 defaultValue(CodeBlock.of(format, *args))
152
153 public fun defaultValue(codeBlock: CodeBlock?): Builder = apply {
154 this.defaultValue = codeBlock
155 }
156
157 public fun build(): ParameterSpec = ParameterSpec(this)
158 }
159
160 public companion object {
161 @DelicateKotlinPoetApi(
162 message = "Element APIs don't give complete information on Kotlin types. Consider using" +
163 " the kotlinpoet-metadata APIs instead.",
164 )
165 @JvmStatic
166 public fun get(element: VariableElement): ParameterSpec {
167 val name = element.simpleName.toString()
168 val type = element.asType().asTypeName()
169 return builder(name, type)
170 .build()
171 }
172
173 @DelicateKotlinPoetApi(
174 message = "Element APIs don't give complete information on Kotlin types. Consider using" +
175 " the kotlinpoet-metadata APIs instead.",
176 )
177 @JvmStatic
178 public fun parametersOf(method: ExecutableElement): List<ParameterSpec> =
179 method.parameters.map(::get)
180
181 @JvmStatic public fun builder(
182 name: String,
183 type: TypeName,
184 vararg modifiers: KModifier,
185 ): Builder {
186 return Builder(name, type).addModifiers(*modifiers)
187 }
188
189 @JvmStatic public fun builder(name: String, type: Type, vararg modifiers: KModifier): Builder =
190 builder(name, type.asTypeName(), *modifiers)
191
192 @JvmStatic public fun builder(
193 name: String,
194 type: KClass<*>,
195 vararg modifiers: KModifier,
196 ): Builder = builder(name, type.asTypeName(), *modifiers)
197
198 @JvmStatic public fun builder(
199 name: String,
200 type: TypeName,
201 modifiers: Iterable<KModifier>,
202 ): Builder {
203 return Builder(name, type).addModifiers(modifiers)
204 }
205
206 @JvmStatic public fun builder(
207 name: String,
208 type: Type,
209 modifiers: Iterable<KModifier>,
210 ): Builder = builder(name, type.asTypeName(), modifiers)
211
212 @JvmStatic public fun builder(
213 name: String,
214 type: KClass<*>,
215 modifiers: Iterable<KModifier>,
216 ): Builder = builder(name, type.asTypeName(), modifiers)
217
218 @JvmStatic public fun unnamed(type: KClass<*>): ParameterSpec = unnamed(type.asTypeName())
219
220 @JvmStatic public fun unnamed(type: Type): ParameterSpec = unnamed(type.asTypeName())
221
222 @JvmStatic public fun unnamed(type: TypeName): ParameterSpec = Builder("", type).build()
223 }
224 }
225
226 // From https://kotlinlang.org/spec/syntax-and-grammar.html#grammar-rule-parameterModifier
227 private val ALLOWED_PARAMETER_MODIFIERS = setOf(VARARG, NOINLINE, CROSSINLINE)
228
emitnull229 internal fun List<ParameterSpec>.emit(
230 codeWriter: CodeWriter,
231 forceNewLines: Boolean = false,
232 emitBlock: (ParameterSpec) -> Unit = { it.emit(codeWriter) },
<lambda>null233 ) = with(codeWriter) {
234 emit("(")
235
236 if (isNotEmpty()) {
237 val emitNewLines = size > 2 || forceNewLines
238 if (emitNewLines) {
239 emit("\n")
240 indent(1)
241 }
242 forEachIndexed { index, parameter ->
243 if (index > 0) {
244 emit(if (emitNewLines) "\n" else ", ")
245 }
246 emitBlock(parameter)
247 if (emitNewLines) {
248 emit(",")
249 }
250 }
251 if (emitNewLines) {
252 unindent(1)
253 emit("\n")
254 }
255 }
256 emit(")")
257 }
258