blob: a73ff50ae90757566dc470479703ab1cb81fd7da [file] [log] [blame]
/*
* Copyright (C) 2015 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.squareup.kotlinpoet
import com.squareup.kotlinpoet.ClassName.Companion.asClassName
import com.squareup.kotlinpoet.FunSpec.Companion.CONSTRUCTOR
import com.squareup.kotlinpoet.FunSpec.Companion.GETTER
import com.squareup.kotlinpoet.FunSpec.Companion.SETTER
import com.squareup.kotlinpoet.KModifier.VARARG
import com.squareup.kotlinpoet.TypeName.Companion.asTypeName
import com.squareup.kotlinpoet.TypeVariableName.Companion.asTypeVariableName
import java.io.IOException
import java.io.StringWriter
import java.lang.reflect.Type
import javax.lang.model.SourceVersion
import javax.lang.model.element.ExecutableElement
import javax.lang.model.element.Modifier
import javax.lang.model.type.DeclaredType
import javax.lang.model.type.ExecutableType
import javax.lang.model.type.TypeVariable
import javax.lang.model.util.Types
import kotlin.reflect.KClass
/** A generated function declaration. */
class FunSpec private constructor(builder: Builder) {
val name = builder.name
val kdoc = builder.kdoc.build()
val annotations = builder.annotations.toImmutableList()
val modifiers = builder.modifiers.toImmutableSet()
val typeVariables = builder.typeVariables.toImmutableList()
val receiverType = builder.receiverType
val returnType = builder.returnType
val parameters = builder.parameters.toImmutableList()
val exceptions = builder.exceptions.toImmutableList()
val code = builder.code.build()
init {
require(code.isEmpty() || !builder.modifiers.contains(KModifier.ABSTRACT)) {
"abstract function ${builder.name} cannot have code"
}
require(name != SETTER || parameters.size == 1) {
"$name must have exactly one parameter"
}
}
internal fun parameter(name: String): ParameterSpec? {
for (parameter in parameters) {
if (parameter.name == name) return parameter
}
return null
}
@Throws(IOException::class)
internal fun emit(
codeWriter: CodeWriter,
enclosingName: String?,
implicitModifiers: Set<KModifier>) {
codeWriter.emitKdoc(kdoc)
codeWriter.emitAnnotations(annotations, false)
codeWriter.emitModifiers(modifiers, implicitModifiers)
if (!isConstructor && !name.isAccessor) {
codeWriter.emit("fun ")
}
if (typeVariables.isNotEmpty()) {
codeWriter.emitTypeVariables(typeVariables)
codeWriter.emit(" ")
}
if (isConstructor) {
codeWriter.emitCode("constructor", enclosingName)
} else if (name == GETTER) {
codeWriter.emitCode("get")
} else if (name == SETTER) {
codeWriter.emitCode("set")
} else {
if (receiverType != null) {
codeWriter.emitCode("%T.", receiverType)
}
codeWriter.emitCode("%L", name)
}
emitParameterList(codeWriter)
if (returnType != null) {
codeWriter.emitCode(": %T", returnType)
}
if (exceptions.isNotEmpty()) {
codeWriter.emitWrappingSpace().emit("throws")
var firstException = true
for (exception in exceptions) {
if (!firstException) codeWriter.emit(",")
codeWriter.emitWrappingSpace().emitCode("%T", exception)
firstException = false
}
}
if (modifiers.contains(KModifier.ABSTRACT) || modifiers.contains(KModifier.EXTERNAL)) {
codeWriter.emit("\n")
return
}
val asExpressionBody = code.withoutPrefix(EXPRESSION_BODY_PREFIX)
if (asExpressionBody != null) {
codeWriter.indent()
codeWriter.emitCode(" =%W%[")
codeWriter.emitCode(asExpressionBody)
codeWriter.unindent()
} else {
codeWriter.emit(" {\n")
codeWriter.indent()
codeWriter.emitCode(code)
codeWriter.unindent()
codeWriter.emit("}\n")
}
}
@Throws(IOException::class)
internal fun emitParameterList(codeWriter: CodeWriter) {
codeWriter.emit("(")
parameters.forEachIndexed { index, parameter ->
if (index > 0) codeWriter.emit(",").emitWrappingSpace()
parameter.emit(codeWriter)
}
codeWriter.emit(")")
}
val isConstructor: Boolean
get() = name.isConstructor
val isAccessor: Boolean
get() = name.isAccessor
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null) return false
if (javaClass != other.javaClass) return false
return toString() == other.toString()
}
override fun hashCode() = toString().hashCode()
override fun toString(): String {
val out = StringWriter()
try {
val codeWriter = CodeWriter(out)
emit(codeWriter, "Constructor", TypeSpec.Kind.CLASS.implicitFunctionModifiers)
return out.toString()
} catch (e: IOException) {
throw AssertionError()
}
}
fun toBuilder(): Builder {
val builder = Builder(name)
builder.kdoc.add(kdoc)
builder.annotations += annotations
builder.modifiers += modifiers
builder.typeVariables += typeVariables
builder.returnType = returnType
builder.parameters += parameters
builder.exceptions += exceptions
builder.code.add(code)
return builder
}
class Builder internal constructor(internal val name: String) {
internal val kdoc = CodeBlock.builder()
internal val annotations = mutableListOf<AnnotationSpec>()
internal val modifiers = mutableListOf<KModifier>()
internal val typeVariables = mutableListOf<TypeVariableName>()
internal var receiverType: TypeName? = null
internal var returnType: TypeName? = null
internal val parameters = mutableListOf<ParameterSpec>()
internal val exceptions = mutableSetOf<TypeName>()
internal val code = CodeBlock.builder()
init {
require(name.isConstructor || name.isAccessor || SourceVersion.isName(name)) {
"not a valid name: $name"
}
}
fun addKdoc(format: String, vararg args: Any) = apply {
kdoc.add(format, *args)
}
fun addKdoc(block: CodeBlock) = apply {
kdoc.add(block)
}
fun addAnnotations(annotationSpecs: Iterable<AnnotationSpec>) = apply {
this.annotations += annotationSpecs
}
fun addAnnotation(annotationSpec: AnnotationSpec) = apply {
annotations += annotationSpec
}
fun addAnnotation(annotation: ClassName) = apply {
annotations += AnnotationSpec.builder(annotation).build()
}
fun addAnnotation(annotation: Class<*>) = addAnnotation(annotation.asClassName())
fun addAnnotation(annotation: KClass<*>) = addAnnotation(annotation.asClassName())
fun addModifiers(vararg modifiers: KModifier) = apply {
this.modifiers += modifiers
}
fun addModifiers(modifiers: Iterable<KModifier>) = apply {
this.modifiers += modifiers
}
fun jvmModifiers(modifiers: Iterable<Modifier>) {
var visibility = KModifier.INTERNAL
for (modifier in modifiers) {
when (modifier) {
Modifier.PUBLIC -> visibility = KModifier.PUBLIC
Modifier.PROTECTED -> visibility = KModifier.PROTECTED
Modifier.PRIVATE -> visibility = KModifier.PRIVATE
Modifier.ABSTRACT -> this.modifiers += KModifier.ABSTRACT
Modifier.FINAL -> this.modifiers += KModifier.FINAL
Modifier.NATIVE -> this.modifiers += KModifier.EXTERNAL
Modifier.DEFAULT -> {}
Modifier.STATIC -> addAnnotation(JvmStatic::class)
Modifier.SYNCHRONIZED -> addAnnotation(Synchronized::class)
Modifier.STRICTFP -> addAnnotation(Strictfp::class)
else -> throw IllegalArgumentException("unexpected fun modifier $modifier")
}
}
this.modifiers += visibility
}
fun addTypeVariables(typeVariables: Iterable<TypeVariableName>) = apply {
check(!name.isAccessor) { "$name cannot have type variables" }
this.typeVariables += typeVariables
}
fun addTypeVariable(typeVariable: TypeVariableName) = apply {
check(!name.isAccessor) { "$name cannot have type variables" }
typeVariables += typeVariable
}
fun receiver(receiverType: TypeName) = apply {
check(!name.isConstructor) { "$name cannot have receiver type" }
this.receiverType = receiverType
}
fun receiver(receiverType: Type) = receiver(receiverType.asTypeName())
fun receiver(receiverType: KClass<*>) = receiver(receiverType.asTypeName())
fun returns(returnType: TypeName) = apply {
check(!name.isConstructor && !name.isAccessor) { "$name cannot have a return type" }
this.returnType = returnType
}
fun returns(returnType: Type) = returns(returnType.asTypeName())
fun returns(returnType: KClass<*>) = returns(returnType.asTypeName())
fun addParameters(parameterSpecs: Iterable<ParameterSpec>) = apply {
for (parameterSpec in parameterSpecs) {
addParameter(parameterSpec)
}
}
fun addParameter(parameterSpec: ParameterSpec) = apply {
check(name != GETTER) { "$name cannot have parameters" }
check(name != SETTER || parameters.size == 0) { "$name can have only one parameter" }
parameters += parameterSpec
}
fun addParameter(name: String, type: TypeName, vararg modifiers: KModifier)
= addParameter(ParameterSpec.builder(name, type, *modifiers).build())
fun addParameter(name: String, type: Type, vararg modifiers: KModifier)
= addParameter(name, type.asTypeName(), *modifiers)
fun addParameter(name: String, type: KClass<*>, vararg modifiers: KModifier)
= addParameter(name, type.asTypeName(), *modifiers)
fun addCode(format: String, vararg args: Any) = apply {
code.add(format, *args)
}
fun addNamedCode(format: String, args: Map<String, *>) = apply {
code.addNamed(format, args)
}
fun addCode(codeBlock: CodeBlock) = apply {
code.add(codeBlock)
}
fun addComment(format: String, vararg args: Any) = apply {
code.add("// " + format + "\n", *args)
}
/**
* @param controlFlow the control flow construct and its code, such as "if (foo == 5)".
* * Shouldn't contain braces or newline characters.
*/
fun beginControlFlow(controlFlow: String, vararg args: Any) = apply {
code.beginControlFlow(controlFlow, *args)
}
/**
* @param controlFlow the control flow construct and its code, such as "else if (foo == 10)".
* * Shouldn't contain braces or newline characters.
*/
fun nextControlFlow(controlFlow: String, vararg args: Any) = apply {
code.nextControlFlow(controlFlow, *args)
}
fun endControlFlow() = apply {
code.endControlFlow()
}
fun addStatement(format: String, vararg args: Any) = apply {
code.addStatement(format, *args)
}
fun build() = FunSpec(this)
}
companion object {
internal const val CONSTRUCTOR = "constructor()"
internal const val GETTER = "get()"
internal const val SETTER = "set()"
@JvmStatic fun builder(name: String): Builder {
return Builder(name)
}
@JvmStatic fun constructorBuilder(): Builder {
return Builder(CONSTRUCTOR)
}
@JvmStatic fun getterBuilder(): Builder {
return Builder(GETTER)
}
@JvmStatic fun setterBuilder(): Builder {
return Builder(SETTER)
}
/**
* Returns a new fun spec builder that overrides `method`.
*
* This will copy its visibility modifiers, type parameters, return type, name, parameters, and
* throws declarations. An `override` modifier will be added.
*/
@JvmStatic fun overriding(method: ExecutableElement): Builder {
var modifiers: MutableSet<Modifier> = method.modifiers
require(!modifiers.contains(Modifier.PRIVATE)
&& !modifiers.contains(Modifier.FINAL)
&& !modifiers.contains(Modifier.STATIC)) {
"cannot override method with modifiers: $modifiers"
}
val methodName = method.simpleName.toString()
val funBuilder = FunSpec.builder(methodName)
funBuilder.addModifiers(KModifier.OVERRIDE)
modifiers = modifiers.toMutableSet()
modifiers.remove(Modifier.ABSTRACT)
funBuilder.jvmModifiers(modifiers)
for (typeParameterElement in method.typeParameters) {
val typeVariable = typeParameterElement.asType() as TypeVariable
funBuilder.addTypeVariable(typeVariable.asTypeVariableName())
}
funBuilder.returns(method.returnType.asTypeName())
funBuilder.addParameters(ParameterSpec.parametersOf(method))
if (method.isVarArgs) {
funBuilder.parameters[funBuilder.parameters.lastIndex] = funBuilder.parameters.last()
.toBuilder()
.addModifiers(VARARG)
.build()
}
if(method.thrownTypes.isNotEmpty()) {
val throwsValueString = method.thrownTypes.joinToString { "%T::class" }
funBuilder.addAnnotation(AnnotationSpec.builder(Throws::class)
.addMember("value", throwsValueString, *method.thrownTypes.toTypedArray())
.build())
}
return funBuilder
}
/**
* Returns a new function spec builder that overrides `method` as a member of `enclosing`. This
* will resolve type parameters: for example overriding [Comparable.compareTo] in a type that
* implements `Comparable<Movie>`, the `T` parameter will be resolved to `Movie`.
*
* This will copy its visibility modifiers, type parameters, return type, name, parameters, and
* throws declarations. An `override` modifier will be added.
*/
@JvmStatic fun overriding(
method: ExecutableElement, enclosing: DeclaredType, types: Types): Builder {
val executableType = types.asMemberOf(enclosing, method) as ExecutableType
val resolvedParameterTypes = executableType.parameterTypes
val resolvedReturnType = executableType.returnType
val builder = overriding(method)
builder.returns(resolvedReturnType.asTypeName())
var i = 0
val size = builder.parameters.size
while (i < size) {
val parameter = builder.parameters[i]
val type = resolvedParameterTypes[i].asTypeName()
builder.parameters[i] = parameter.toBuilder(parameter.name, type).build()
i++
}
return builder
}
}
}
internal val String.isConstructor: Boolean
get() = this == CONSTRUCTOR
internal val String.isAccessor: Boolean
get() = this == GETTER || this == SETTER
internal val EXPRESSION_BODY_PREFIX = CodeBlock.of("%[return ")