blob: b465f47345d545acf8233a0eb29e68bfcaecf392 [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.Util.requireExactlyOneOf
import java.io.IOException
import java.io.StringWriter
import java.lang.reflect.Type
import java.util.Locale
import javax.lang.model.SourceVersion
import javax.lang.model.element.Element
import kotlin.reflect.KClass
/** A generated class, interface, or enum declaration. */
class TypeSpec private constructor(builder: TypeSpec.Builder) {
val kind = builder.kind
val name = builder.name
val anonymousTypeArguments = builder.anonymousTypeArguments
val kdoc = builder.kdoc.build()
val annotations: List<AnnotationSpec> = Util.immutableList(builder.annotations)
val modifiers: Set<KModifier> = Util.immutableSet(builder.modifiers)
val typeVariables: List<TypeVariableName> = Util.immutableList(builder.typeVariables)
val primaryConstructor = builder.primaryConstructor
val superclass = builder.superclass
val superinterfaces: List<TypeName> = Util.immutableList(builder.superinterfaces)
val enumConstants: Map<String, TypeSpec> = Util.immutableMap(builder.enumConstants)
val propertySpecs: List<PropertySpec> = Util.immutableList(builder.propertySpecs)
val initializerBlock = builder.initializerBlock.build()
val funSpecs: List<FunSpec> = Util.immutableList(builder.funSpecs)
val typeSpecs: List<TypeSpec> = Util.immutableList(builder.typeSpecs)
val originatingElements: List<Element>
init {
val originatingElementsMutable = mutableListOf<Element>()
originatingElementsMutable += builder.originatingElements
for (typeSpec in builder.typeSpecs) {
originatingElementsMutable += typeSpec.originatingElements
}
this.originatingElements = Util.immutableList(originatingElementsMutable)
}
fun toBuilder(): Builder {
val builder = Builder(kind, name, anonymousTypeArguments)
builder.kdoc.add(kdoc)
builder.annotations += annotations
builder.modifiers += modifiers
builder.typeVariables += typeVariables
builder.superclass = superclass
builder.superinterfaces += superinterfaces
builder.enumConstants += enumConstants
builder.propertySpecs += propertySpecs
builder.funSpecs += funSpecs
builder.typeSpecs += typeSpecs
builder.initializerBlock.add(initializerBlock)
return builder
}
@Throws(IOException::class)
internal fun emit(codeWriter: CodeWriter, enumName: String?, implicitModifiers: Set<KModifier>) {
// Nested classes interrupt wrapped line indentation. Stash the current wrapping state and put
// it back afterwards when this type is complete.
val previousStatementLine = codeWriter.statementLine
codeWriter.statementLine = -1
try {
if (enumName != null) {
codeWriter.emitKdoc(kdoc)
codeWriter.emitAnnotations(annotations, false)
codeWriter.emit("%L", enumName)
if (!anonymousTypeArguments!!.formatParts.isEmpty()) {
codeWriter.emit("(")
codeWriter.emit(anonymousTypeArguments)
codeWriter.emit(")")
}
if (propertySpecs.isEmpty() && funSpecs.isEmpty() && typeSpecs.isEmpty()) {
return // Avoid unnecessary braces "{}".
}
codeWriter.emit(" {\n")
} else if (anonymousTypeArguments != null) {
val supertype = if (!superinterfaces.isEmpty()) superinterfaces[0] else superclass
codeWriter.emit("new %T(", supertype)
codeWriter.emit(anonymousTypeArguments)
codeWriter.emit(") {\n")
} else {
codeWriter.emitKdoc(kdoc)
codeWriter.emitAnnotations(annotations, false)
codeWriter.emitJavaModifiers(modifiers, implicitModifiers)
if (kind == Kind.ANNOTATION) {
codeWriter.emit("%L %L", "@interface", name)
} else {
codeWriter.emit("%L %L", kind.name.toLowerCase(Locale.US), name)
}
codeWriter.emitTypeVariables(typeVariables)
primaryConstructor?.emitParameterList(codeWriter)
val extendsTypes: List<TypeName>
val implementsTypes: List<TypeName>
if (kind == Kind.INTERFACE) {
extendsTypes = superinterfaces
implementsTypes = emptyList()
} else {
extendsTypes = if (superclass == ANY) emptyList() else listOf(superclass)
implementsTypes = superinterfaces
}
if (!extendsTypes.isEmpty()) {
codeWriter.emit(" extends")
var firstType = true
for (type in extendsTypes) {
if (!firstType) codeWriter.emit(",")
codeWriter.emit(" %T", type)
firstType = false
}
}
if (!implementsTypes.isEmpty()) {
codeWriter.emit(" implements")
var firstType = true
for (type in implementsTypes) {
if (!firstType) codeWriter.emit(",")
codeWriter.emit(" %T", type)
firstType = false
}
}
codeWriter.emit(" {\n")
}
codeWriter.pushType(this)
codeWriter.indent()
var firstMember = true
val i = enumConstants.entries.iterator()
while (i.hasNext()) {
val enumConstant = i.next()
if (!firstMember) codeWriter.emit("\n")
enumConstant.value
.emit(codeWriter, enumConstant.key, emptySet<KModifier>())
firstMember = false
if (i.hasNext()) {
codeWriter.emit(",\n")
} else if (!propertySpecs.isEmpty() || !funSpecs.isEmpty() || !typeSpecs.isEmpty()) {
codeWriter.emit(";\n")
} else {
codeWriter.emit("\n")
}
}
// Non-static properties.
for (propertySpec in propertySpecs) {
if (!firstMember) codeWriter.emit("\n")
propertySpec.emit(codeWriter, kind.implicitPropertyModifiers)
firstMember = false
}
if (primaryConstructor != null && !primaryConstructor.code.isEmpty()) {
codeWriter.emit("init {\n")
codeWriter.indent()
codeWriter.emit(primaryConstructor.code)
codeWriter.unindent()
codeWriter.emit("}\n")
}
// Initializer block.
if (!initializerBlock.isEmpty()) {
if (!firstMember) codeWriter.emit("\n")
codeWriter.emit(initializerBlock)
firstMember = false
}
// Constructors.
for (funSpec in funSpecs) {
if (!funSpec.isConstructor) continue
if (!firstMember) codeWriter.emit("\n")
funSpec.emit(codeWriter, name!!, kind.implicitFunctionModifiers)
firstMember = false
}
// Functions (static and non-static).
for (funSpec in funSpecs) {
if (funSpec.isConstructor) continue
if (!firstMember) codeWriter.emit("\n")
funSpec.emit(codeWriter, name, kind.implicitFunctionModifiers)
firstMember = false
}
// Types.
for (typeSpec in typeSpecs) {
if (!firstMember) codeWriter.emit("\n")
typeSpec.emit(codeWriter, null, setOf())
firstMember = false
}
codeWriter.unindent()
codeWriter.popType()
codeWriter.emit("}")
if (enumName == null && anonymousTypeArguments == null) {
codeWriter.emit("\n") // If this type isn't also a value, include a trailing newline.
}
} finally {
codeWriter.statementLine = previousStatementLine
}
}
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(): Int {
return toString().hashCode()
}
override fun toString(): String {
val out = StringWriter()
try {
val codeWriter = CodeWriter(out)
emit(codeWriter, null, emptySet<KModifier>())
return out.toString()
} catch (e: IOException) {
throw AssertionError()
}
}
enum class Kind(
internal val implicitPropertyModifiers: Set<KModifier>,
internal val implicitFunctionModifiers: Set<KModifier>) {
CLASS(
setOf(KModifier.PUBLIC),
setOf(KModifier.PUBLIC)),
INTERFACE(
setOf(KModifier.PUBLIC),
setOf(KModifier.PUBLIC, KModifier.ABSTRACT)),
ENUM(
setOf(KModifier.PUBLIC),
setOf(KModifier.PUBLIC)),
ANNOTATION(
emptySet(),
setOf(KModifier.PUBLIC, KModifier.ABSTRACT))
}
class Builder internal constructor(
internal val kind: Kind,
internal val name: String?,
internal val anonymousTypeArguments: CodeBlock?) {
internal val kdoc = CodeBlock.builder()
internal val annotations = mutableListOf<AnnotationSpec>()
internal val modifiers = mutableListOf<KModifier>()
internal val typeVariables = mutableListOf<TypeVariableName>()
internal var primaryConstructor : FunSpec? = null
internal var superclass: TypeName = ANY
internal val superinterfaces = mutableListOf<TypeName>()
internal val enumConstants = mutableMapOf<String, TypeSpec>()
internal val propertySpecs = mutableListOf<PropertySpec>()
internal val initializerBlock = CodeBlock.builder()
internal val funSpecs = mutableListOf<FunSpec>()
internal val typeSpecs = mutableListOf<TypeSpec>()
internal val originatingElements = mutableListOf<Element>()
init {
require(name == null || SourceVersion.isName(name)) { "not a valid name: $name" }
}
fun addKdoc(format: String, vararg args: Any): Builder {
kdoc.add(format, *args)
return this
}
fun addKdoc(block: CodeBlock): Builder {
kdoc.add(block)
return this
}
fun addAnnotations(annotationSpecs: Iterable<AnnotationSpec>): Builder {
annotations += annotationSpecs
return this
}
fun addAnnotation(annotationSpec: AnnotationSpec): Builder {
annotations += annotationSpec
return this
}
fun addAnnotation(annotation: ClassName)
= addAnnotation(AnnotationSpec.builder(annotation).build())
fun addAnnotation(annotation: Class<*>) = addAnnotation(ClassName.get(annotation))
fun addAnnotation(annotation: KClass<*>) = addAnnotation(ClassName.get(annotation))
fun addModifiers(vararg modifiers: KModifier): Builder {
check(anonymousTypeArguments == null) { "forbidden on anonymous types." }
this.modifiers += modifiers
return this
}
fun addTypeVariables(typeVariables: Iterable<TypeVariableName>): Builder {
check(anonymousTypeArguments == null) { "forbidden on anonymous types." }
this.typeVariables += typeVariables
return this
}
fun addTypeVariable(typeVariable: TypeVariableName): Builder {
check(anonymousTypeArguments == null) { "forbidden on anonymous types." }
typeVariables += typeVariable
return this
}
fun primaryConstructor(primaryConstructor: FunSpec?): Builder {
check(kind == Kind.CLASS || kind == Kind.ENUM) { "$kind can't have initializer blocks" }
if (primaryConstructor != null) {
require (primaryConstructor.isConstructor) {
"expected a constructor but was ${primaryConstructor.name}"
}
}
this.primaryConstructor = primaryConstructor
return this
}
fun superclass(superclass: TypeName): Builder {
check(kind == Kind.CLASS) { "only classes have super classes, not $kind" }
check(this.superclass === ANY) { "superclass already set to ${this.superclass}" }
this.superclass = superclass
return this
}
fun superclass(superclass: Type): Builder {
return superclass(TypeName.get(superclass))
}
fun addSuperinterfaces(superinterfaces: Iterable<TypeName>): Builder {
this.superinterfaces += superinterfaces
return this
}
fun addSuperinterface(superinterface: TypeName): Builder {
superinterfaces += superinterface
return this
}
fun addSuperinterface(superinterface: Type)
= addSuperinterface(TypeName.get(superinterface))
fun addSuperinterface(superinterface: KClass<*>)
= addSuperinterface(TypeName.get(superinterface))
@JvmOverloads fun addEnumConstant(
name: String,
typeSpec: TypeSpec = anonymousClassBuilder("").build()): Builder {
check(kind == Kind.ENUM) { "${this.name} is not enum" }
require(typeSpec.anonymousTypeArguments != null) {
"enum constants must have anonymous type arguments" }
require(SourceVersion.isName(name)) { "not a valid enum constant: $name" }
enumConstants.put(name, typeSpec)
return this
}
fun addProperties(propertySpecs: Iterable<PropertySpec>): Builder {
this.propertySpecs += propertySpecs
return this
}
fun addProperty(propertySpec: PropertySpec): Builder {
check(kind == Kind.CLASS || kind == Kind.ENUM) {
"cannot add property $propertySpec to $kind"
}
propertySpecs += propertySpec
return this
}
fun addProperty(type: TypeName, name: String, vararg modifiers: KModifier)
= addProperty(PropertySpec.builder(type, name, *modifiers).build())
fun addProperty(type: Type, name: String, vararg modifiers: KModifier)
= addProperty(TypeName.get(type), name, *modifiers)
fun addProperty(type: KClass<*>, name: String, vararg modifiers: KModifier)
= addProperty(TypeName.get(type), name, *modifiers)
fun addInitializerBlock(block: CodeBlock): Builder {
check(kind == Kind.CLASS || kind == Kind.ENUM) { "$kind can't have initializer blocks" }
initializerBlock.add("{\n")
.indent()
.add(block)
.unindent()
.add("}\n")
return this
}
fun addFunctions(funSpecs: Iterable<FunSpec>): Builder {
this.funSpecs += funSpecs
return this
}
fun addFun(funSpec: FunSpec): Builder {
if (kind == Kind.INTERFACE) {
requireExactlyOneOf(funSpec.modifiers, KModifier.ABSTRACT)
requireExactlyOneOf(funSpec.modifiers, KModifier.PUBLIC, KModifier.PRIVATE)
} else if (kind == Kind.ANNOTATION) {
check(funSpec.modifiers == kind.implicitFunctionModifiers) {
"$kind $name.${funSpec.name} requires modifiers ${kind.implicitFunctionModifiers}" }
}
if (kind != Kind.ANNOTATION) {
check(funSpec.defaultValue == null) {
"$kind $name.${funSpec.name} cannot have a default value" }
}
funSpecs += funSpec
return this
}
fun addTypes(typeSpecs: Iterable<TypeSpec>): Builder {
this.typeSpecs += typeSpecs
return this
}
fun addType(typeSpec: TypeSpec): Builder {
typeSpecs += typeSpec
return this
}
fun addOriginatingElement(originatingElement: Element): Builder {
originatingElements += originatingElement
return this
}
fun build(): TypeSpec {
require(kind != Kind.ENUM || !enumConstants.isEmpty()) {
"at least one enum constant is required for $name" }
val isAbstract = modifiers.contains(KModifier.ABSTRACT) || kind != Kind.CLASS
for (funSpec in funSpecs) {
require(isAbstract || !funSpec.modifiers.contains(KModifier.ABSTRACT)) {
"non-abstract type $name cannot declare abstract function ${funSpec.name}" }
}
val superclassIsAny = superclass == ANY
val interestingSupertypeCount = (if (superclassIsAny) 0 else 1) + superinterfaces.size
require(anonymousTypeArguments == null || interestingSupertypeCount <= 1) {
"anonymous type has too many supertypes" }
return TypeSpec(this)
}
}
companion object {
@JvmStatic fun classBuilder(name: String) = Builder(Kind.CLASS, name, null)
@JvmStatic fun classBuilder(className: ClassName) = classBuilder(className.simpleName())
@JvmStatic fun interfaceBuilder(name: String) = Builder(Kind.INTERFACE, name, null)
@JvmStatic fun interfaceBuilder(className: ClassName) = interfaceBuilder(className.simpleName())
@JvmStatic fun enumBuilder(name: String) = Builder(Kind.ENUM, name, null)
@JvmStatic fun enumBuilder(className: ClassName) = enumBuilder(className.simpleName())
@JvmStatic fun anonymousClassBuilder(typeArgumentsFormat: String, vararg args: Any): Builder {
return Builder(Kind.CLASS, null, CodeBlock.builder()
.add(typeArgumentsFormat, *args)
.build())
}
@JvmStatic fun annotationBuilder(name: String) = Builder(Kind.ANNOTATION, name, null)
@JvmStatic fun annotationBuilder(className: ClassName)
= annotationBuilder(className.simpleName())
}
}