All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.apollographql.apollo.compiler.InputTypeSpecBuilder.kt Maven / Gradle / Ivy

/**
 * Copyright 2018-2019 Amazon.com,
 * Inc. or its affiliates. All Rights Reserved.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

package com.apollographql.apollo.compiler

import com.apollographql.apollo.api.InputFieldMarshaller
import com.apollographql.apollo.api.InputFieldWriter
import com.apollographql.apollo.compiler.ir.CodeGenerationContext
import com.apollographql.apollo.compiler.ir.TypeDeclarationField
import com.squareup.javapoet.*
import java.io.IOException
import javax.lang.model.element.Modifier
import com.apollographql.apollo.compiler.ClassNames.S3ObjectInput

class InputTypeSpecBuilder(
    val name: String,
    val fields: List,
    val context: CodeGenerationContext
) {
  private val objectClassName = ClassName.get("", name.capitalize())

  fun build(): TypeSpec =
          if (objectClassName.toString().equals("S3ObjectInput")) {
              TypeSpec.classBuilder(objectClassName)
                      .addAnnotation(Annotations.GENERATED_BY_APOLLO)
                      .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                      .addSuperinterface(S3ObjectInput)
                      .addConstructor()
                      .addFields()
                      .addBuilder()
                      .addMethod(marshallerMethodSpec())
                      .build()

          } else {
              TypeSpec.classBuilder(objectClassName)
                      .addAnnotation(Annotations.GENERATED_BY_APOLLO)
                      .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                      .addSuperinterface(ClassNames.INPUT_TYPE)
                      .addConstructor()
                      .addFields()
                      .addBuilder()
                      .addMethod(marshallerMethodSpec())
                      .build()
          }

  private fun TypeSpec.Builder.addConstructor(): TypeSpec.Builder {
    val fieldInitializeCodeBuilder = fields.map {
      CodeBlock.of("this.\$L = \$L;\n", it.name.decapitalize(), it.name.decapitalize())
    }.fold(CodeBlock.builder(), CodeBlock.Builder::add)

    return addMethod(MethodSpec
        .constructorBuilder()
        .addParameters(fields.map {
          ParameterSpec.builder(it.javaTypeName(context), it.name.decapitalize()).build()
        })
        .addCode(fieldInitializeCodeBuilder.build())
        .build()
    )
  }

  private fun TypeSpec.Builder.addBuilder(): TypeSpec.Builder {
    if (fields.isEmpty()) {
      return this
    } else {
      val builderFields = fields.map { it.name.decapitalize() to it.javaTypeName(context) }
      val builderFieldDefaultValues = fields
          .filterNot {
            // ignore any custom type default values for now as we don't support them
            val normalizedType = it.type.removeSuffix("!").removeSurrounding("[", "]").removeSuffix("!")
            normalizedType.isCustomScalarType(context)
          }
          .associate { it.name.decapitalize() to it.defaultValue }
      val javaDocs = fields
          .filter { !it.description.isNullOrBlank() }
          .associate { it.name.decapitalize() to it.description }
      return addMethod(BuilderTypeSpecBuilder.builderFactoryMethod())
          .addType(
              BuilderTypeSpecBuilder(
                  targetObjectClassName = objectClassName,
                  fields = builderFields,
                  fieldDefaultValues = builderFieldDefaultValues,
                  fieldJavaDocs = javaDocs,
                  typeDeclarations = context.typeDeclarations
              ).build()
          )
    }
  }

  private fun marshallerMethodSpec(): MethodSpec {
    val writeCode = fields
        .map {
          InputFieldSpec.build(
              name = it.name,
              graphQLType = it.type,
              context = context
          )
        }
        .map {
          it.writeValueCode(
              writerParam = CodeBlock.of("\$L", WRITER_PARAM.name),
              marshaller = CodeBlock.of("$MARSHALLER_PARAM_NAME()")
          )
        }
        .fold(CodeBlock.builder(), CodeBlock.Builder::add)
        .build()
    val methodSpec = MethodSpec.methodBuilder("marshal")
        .addModifiers(Modifier.PUBLIC)
        .addAnnotation(Override::class.java)
        .addParameter(WRITER_PARAM)
        .addException(IOException::class.java)
        .addCode(writeCode)
        .build()
    val marshallerType = TypeSpec.anonymousClassBuilder("")
        .addSuperinterface(InputFieldMarshaller::class.java)
        .addMethod(methodSpec)
        .build()
    var marshallerSpec = MethodSpec.methodBuilder(MARSHALLER_PARAM_NAME)
            .addModifiers(Modifier.PUBLIC)
            .returns(InputFieldMarshaller::class.java)
            .addStatement("return \$L", marshallerType);
    if (! objectClassName.toString().equals("S3ObjectInput")) {
        marshallerSpec = marshallerSpec.addAnnotation(Annotations.OVERRIDE)
    }
    return marshallerSpec.build()
  }

  private fun TypeSpec.Builder.addFields(): TypeSpec.Builder {
    fun addFieldDefinition(field: TypeDeclarationField) {
      addField(FieldSpec
          .builder(field.javaTypeName(context), field.name.decapitalize())
          .addModifiers(Modifier.PRIVATE, Modifier.FINAL)
          .build())
    }

    fun addFieldAccessor(field: TypeDeclarationField) {
      val optional = !field.type.endsWith("!")
      addMethod(MethodSpec.methodBuilder(field.name.decapitalize())
          .addModifiers(Modifier.PUBLIC)
          .returns(field.javaTypeName(context).unwrapOptionalType())
          .let {
            if (!field.description.isNullOrBlank())
              it.addJavadoc(CodeBlock.of("\$L\n", field.description))
            else
              it
          }
          .addStatement("return this.\$L\$L", field.name.decapitalize(), if (optional) ".value" else "")
          .build())
    }

    fields.forEach { field ->
      addFieldDefinition(field)
      addFieldAccessor(field)
    }

    return this
  }

  private fun TypeDeclarationField.javaTypeName(context: CodeGenerationContext): TypeName {
    return JavaTypeResolver(context, context.typesPackage)
        .resolve(typeName = type, nullableValueType = NullableValueType.INPUT_TYPE)
  }

  companion object {
    private val WRITER_PARAM = ParameterSpec.builder(InputFieldWriter::class.java, "writer").build()
    private const val MARSHALLER_PARAM_NAME = "marshaller"
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy