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

io.vrap.codegen.languages.javalang.client.builder.producers.JavaModelDraftBuilderFileProducer.kt Maven / Gradle / Ivy

Go to download

RAML API client code generators based on the REST Modeling Framework. https://github.com/vrapio/rest-modeling-framework

There is a newer version: 1.0.0-20241120142200
Show newest version
package io.vrap.codegen.languages.javalang.client.builder.producers

import io.vrap.codegen.languages.extensions.hasSubtypes
import io.vrap.codegen.languages.extensions.isPatternProperty
import io.vrap.codegen.languages.extensions.toComment
import io.vrap.codegen.languages.java.base.JavaBaseTypes
import io.vrap.codegen.languages.java.base.JavaSubTemplates
import io.vrap.codegen.languages.java.base.extensions.*
import io.vrap.rmf.codegen.firstUpperCase
import io.vrap.rmf.codegen.di.AllObjectTypes
import io.vrap.rmf.codegen.io.TemplateFile
import io.vrap.rmf.codegen.rendering.FileProducer
import io.vrap.rmf.codegen.rendering.utils.escapeAll
import io.vrap.rmf.codegen.rendering.utils.keepIndentation
import io.vrap.rmf.codegen.types.VrapArrayType
import io.vrap.rmf.codegen.types.VrapObjectType
import io.vrap.rmf.codegen.types.VrapTypeProvider
import io.vrap.rmf.raml.model.types.*
import io.vrap.rmf.raml.model.types.Annotation
import javax.lang.model.SourceVersion


class JavaModelDraftBuilderFileProducer constructor(override val vrapTypeProvider: VrapTypeProvider, @AllObjectTypes private val allObjectTypes: List) : JavaObjectTypeExtensions, JavaEObjectTypeExtensions, FileProducer {

    override fun produceFiles(): List {
        return allObjectTypes.filter { !it.isAbstract() && !it.deprecated() }.map { render(it) }
            .plus(allObjectTypes.filter { it.isAbstract() && it.discriminator != null }.map { renderAbstract(it) })
    }

    fun render(type: ObjectType): TemplateFile {

        val vrapType = vrapTypeProvider.doSwitch(type).toJavaVType() as VrapObjectType
        val extends = listOf("Builder<${vrapType.simpleClassName}>".escapeAll())
            .plus(
                when (val ex = type.getAnnotation("java-builder-implements") ) {
                    is Annotation -> {
                        (ex.value as StringInstance).value.escapeAll()
                    }
                    else -> null
                }
            )
            .filterNotNull()

        val content : String = """
            |package ${vrapType.`package`};
            |
            |${type.imports()}
            |import ${vrapType.`package`}.${vrapType.simpleClassName};
            |import javax.annotation.Nullable;
            |import java.util.*;
            |import java.util.function.Function;
            |import java.time.ZonedDateTime;
            |import io.vrap.rmf.base.client.Builder;
            |import io.vrap.rmf.base.client.utils.Generated;
            |
            |/**
            | * ${vrapType.simpleClassName}Builder
            | * \
            | <${type.builderComment().escapeAll()}> 
            | */
            |<${JavaSubTemplates.generatedAnnotation}>${if (type.markDeprecated() ) """
            |@Deprecated""" else ""}
            |public class ${vrapType.simpleClassName}Builder implements ${extends.joinToString(", ")} {
            |
            |    <${type.fields().escapeAll()}>
            |
            |    <${type.assignments().escapeAll()}>
            |
            |    <${type.getters().escapeAll()}>
            |
            |    /**
            |     * builds ${vrapType.simpleClassName} with checking for non-null required values
            |     * @return ${vrapType.simpleClassName}
            |     */
            |    public ${vrapType.simpleClassName} build() {
            |        <${type.requiredChecks().escapeAll()}>
            |        <${type.buildMethodBody().escapeAll()}>
            |    }
            |    
            |    /**
            |     * builds ${vrapType.simpleClassName} without checking for non-null required values
            |     * @return ${vrapType.simpleClassName}
            |     */
            |    public ${vrapType.simpleClassName} buildUnchecked() {
            |        <${type.buildMethodBody().escapeAll()}>
            |    }
            |
            |    <${type.staticOfMethod().escapeAll()}>
            |
            |    <${type.templateMethod()}>
            |
            |    <${type.getAnnotation("java-builder-mixin")?.value?.value?.let { (it as String).escapeAll()} ?: ""}>
            |}
        """.trimMargin().keepIndentation()

        return TemplateFile(
                relativePath = "${vrapType.`package`}.${vrapType.simpleClassName}Builder".replace(".", "/") + ".java",
                content = content
        )
    }

    fun renderAbstract(type: ObjectType): TemplateFile {

        val vrapType = vrapTypeProvider.doSwitch(type).toJavaVType() as VrapObjectType
        val extends = emptyList()
            .plus(
                when (val ex = type.getAnnotation("java-builder-implements") ) {
                    is Annotation -> {
                        (ex.value as StringInstance).value.escapeAll()
                    }
                    else -> null
                }
            )
            .filterNotNull()

        val content : String = """
            |package ${vrapType.`package`};
            |
            |${type.imports()}
            |import ${vrapType.`package`}.${vrapType.simpleClassName};
            |import javax.annotation.Nullable;
            |import java.util.*;
            |import java.util.function.Function;
            |import java.time.ZonedDateTime;
            |import io.vrap.rmf.base.client.Builder;
            |import io.vrap.rmf.base.client.utils.Generated;
            |
            |/**
            | * ${vrapType.simpleClassName}Builder
            | */
            |<${JavaSubTemplates.generatedAnnotation}>${if (type.markDeprecated() ) """
            |@Deprecated""" else ""}
            |public class ${vrapType.simpleClassName}Builder${if (extends.isNotEmpty()) { " implements ${extends.joinToString(separator = ", ")}" } else ""} {
            |
            |    <${type.subTypeBuilders().escapeAll()}>
            |
            |    <${type.staticOfMethod().escapeAll()}>
            |
            |    <${type.getAnnotation("java-builder-mixin")?.value?.value?.let { (it as String).escapeAll()} ?: ""}>
            |}
        """.trimMargin().keepIndentation()

        return TemplateFile(
            relativePath = "${vrapType.`package`}.${vrapType.simpleClassName}Builder".replace(".", "/") + ".java",
            content = content
        )
    }

    private fun ObjectType.subTypeBuilders() : String {
        if (this.hasSubtypes()) {
            return this.subTypes.plus(this.subTypes.flatMap { it.subTypes }).distinctBy { it.name }
                .asSequence()
                .filterIsInstance()
                .filter { it.getAnnotation("deprecated") == null }
                .filter { it.discriminatorValue != null }
                .sortedBy { anyType -> anyType.name }
                .map {
                    val vrapSubType = vrapTypeProvider.doSwitch(it) as VrapObjectType
                    """
                    |public ${vrapSubType.`package`.toJavaPackage()}.${vrapSubType.simpleClassName}Builder ${it.discriminatorValue.lowerCamelCase()}Builder() {
                    |   return ${vrapSubType.`package`.toJavaPackage()}.${vrapSubType.simpleClassName}Builder.of();
                    |}
                    """.trimMargin()
                }
                .joinToString(separator = "\n")
        }
        return ""
    }

    private fun ObjectType.fields() = this.allProperties
            .filterNot { it.deprecated() }
            .filter { it.name != this.discriminator() }
            .map { it.toField() }
            .joinToString(separator = "\n\n")

    private fun Property.toField() : String {
        val vrapType = this.type.toVrapType()
        return if(this.isPatternProperty()){
            """
                |${this.deprecationAnnotation()}
                |${if(!this.required) "@Nullable" else ""}
                |private Map values = new HashMap<>();
            """.escapeAll().trimMargin().keepIndentation()
        } else if(this.name.equals("interface")) {
            """
                |${this.deprecationAnnotation()}
                |${if (!this.required) "@Nullable" else ""}
                |private ${vrapType.fullClassName()} _interface;
            """.trimMargin()
        }else{
            """
            |${this.deprecationAnnotation()}
            |${if(!this.required) "@Nullable" else ""}
            |private ${vrapType.fullClassName()} ${this.name};
            """.escapeAll().trimMargin().keepIndentation()
        }
    }

    private fun ObjectType.assignments() : String {
        val vrapType = vrapTypeProvider.doSwitch(this).toJavaVType() as VrapObjectType

        return this.allProperties
                .filterNot { it.deprecated() }
                .filter { it.name != this.discriminator() }
                .map { assignment(it, vrapType) }.joinToString(separator = "\n\n")
    }

    private fun assignment(property: Property, type: VrapObjectType) : String {
        val propertyType = property.type;
        val propType = propertyType.toVrapType()
        return if(property.isPatternProperty()) {
            """
                |/**
                |${propertyType.toComment(" * assign pattern properties to the builder")}
                | * @param values properties to be set
                | * @return Builder
                | */
                |${property.deprecationAnnotation()}
                |public ${type.simpleClassName}Builder values(${if (!property.required) "@Nullable" else ""} final Map values){
                |    this.values = values;
                |    return this;
                |}
                |
                |/**
                |${propertyType.toComment(" * assign a pattern property to the builder")}
                | * @param key property name
                | * @param value property value
                | * @return Builder
                | */
                |${property.deprecationAnnotation()}
                |public ${type.simpleClassName}Builder addValue(final String key, final ${propType.fullClassName()} value) {
                |    if (this.values == null) {
                |        values = new HashMap<>();
                |    }
                |    values.put(key, value);
                |    return this;
                |}

            """.escapeAll().trimMargin().keepIndentation()
        } else if (propType is VrapArrayType) {
            var propertyName = property.name
            val propItemType = (property.type as ArrayType).items
            if(SourceVersion.isKeyword(propertyName)) {
                propertyName = "_$propertyName"
            }
            """
                |/**
                |${propertyType.toComment(" * set values to the $propertyName")}
                | * @param $propertyName value to be set
                | * @return Builder
                | */
                |${property.deprecationAnnotation()}
                |public ${type.simpleClassName}Builder $propertyName(${if (!property.required) "@Nullable" else ""} final ${propType.itemType.fullClassName()} ...$propertyName) {
                |    this.$propertyName = new ArrayList<>(Arrays.asList($propertyName));
                |    return this;
                |}
                |
                |/**
                |${propertyType.toComment(" * set value to the $propertyName")}
                | * @param $propertyName value to be set
                | * @return Builder
                | */
                |${property.deprecationAnnotation()}
                |public ${type.simpleClassName}Builder $propertyName(${if (!property.required) "@Nullable" else ""} final ${propType.fullClassName()} $propertyName) {
                |    this.$propertyName = $propertyName;
                |    return this;
                |}
                |
                |/**
                |${propertyType.toComment(" * add values to the $propertyName")}
                | * @param $propertyName value to be set
                | * @return Builder
                | */
                |${property.deprecationAnnotation()}
                |public ${type.simpleClassName}Builder plus${propertyName.firstUpperCase()}(${if (!property.required) "@Nullable" else ""} final ${propType.itemType.fullClassName()} ...$propertyName) {
                |    if (this.$propertyName == null) {
                |        this.$propertyName = new ArrayList<>();
                |    }
                |    this.$propertyName.addAll(Arrays.asList($propertyName));
                |    return this;
                |}
                |
                |${if (propItemType is ObjectType && propItemType.isAbstract() && propItemType.discriminator != null) """
                |/**
                |${propertyType.toComment(" * add a value to the $propertyName using the builder function")}
                | * @param builder function to build the $propertyName value
                | * @return Builder
                | */
                |${property.deprecationAnnotation()}
                |public ${type.simpleClassName}Builder plus${property.name.firstUpperCase()}(Function<${propType.itemType.fullClassName()}Builder, Builder> builder) {
                |    if (this.$propertyName == null) {
                |        this.$propertyName = new ArrayList<>();
                |    }
                |    this.$propertyName.add(builder.apply(${propType.itemType.fullClassName()}Builder.of()).build());
                |    return this;
                |}
                |
                |/**
                |${propertyType.toComment(" * set the value to the $propertyName using the builder function")}
                | * @param builder function to build the $propertyName value
                | * @return Builder
                | */
                |${property.deprecationAnnotation()}
                |public ${type.simpleClassName}Builder with${property.name.firstUpperCase()}(Function<${propType.itemType.fullClassName()}Builder, Builder> builder) {
                |    this.$propertyName = new ArrayList<>();
                |    this.$propertyName.add(builder.apply(${propType.itemType.fullClassName()}Builder.of()).build());
                |    return this;
                |}
                """ else ""}
                |${if (propItemType is ObjectType && !propItemType.isAbstract() && propType.simpleName() != JavaBaseTypes.objectType.simpleName()) """
                |/**
                |${propertyType.toComment(" * add the value to the $propertyName using the builder function")}
                | * @param builder function to build the $propertyName value
                | * @return Builder
                | */
                |${property.deprecationAnnotation()}
                |public ${type.simpleClassName}Builder plus${property.name.firstUpperCase()}(Function<${propType.itemType.fullClassName()}Builder, ${propType.itemType.fullClassName()}Builder> builder) {
                |    if (this.$propertyName == null) {
                |        this.$propertyName = new ArrayList<>();
                |    }
                |    this.$propertyName.add(builder.apply(${propType.itemType.fullClassName()}Builder.of()).build());
                |    return this;
                |}
                |
                |/**
                |${propertyType.toComment(" * set the value to the $propertyName using the builder function")}
                | * @param builder function to build the $propertyName value
                | * @return Builder
                | */
                |${property.deprecationAnnotation()}
                |public ${type.simpleClassName}Builder with${property.name.firstUpperCase()}(Function<${propType.itemType.fullClassName()}Builder, ${propType.itemType.fullClassName()}Builder> builder) {
                |    this.$propertyName = new ArrayList<>();
                |    this.$propertyName.add(builder.apply(${propType.itemType.fullClassName()}Builder.of()).build());
                |    return this;
                |}
                |
                |/**
                |${propertyType.toComment(" * add the value to the $propertyName using the builder function")}
                | * @param builder function to build the $propertyName value
                | * @return Builder
                | */
                |${property.deprecationAnnotation()}
                |public ${type.simpleClassName}Builder add${property.name.firstUpperCase()}(Function<${propType.itemType.fullClassName()}Builder, ${propType.itemType.fullClassName()}> builder) {
                |    return plus${property.name.firstUpperCase()}(builder.apply(${propType.itemType.fullClassName()}Builder.of()));
                |}
                |
                |/**
                |${propertyType.toComment(" * set the value to the $propertyName using the builder function")}
                | * @param builder function to build the $propertyName value
                | * @return Builder
                | */
                |${property.deprecationAnnotation()}
                |public ${type.simpleClassName}Builder set${property.name.firstUpperCase()}(Function<${propType.itemType.fullClassName()}Builder, ${propType.itemType.fullClassName()}> builder) {
                |    return $propertyName(builder.apply(${propType.itemType.fullClassName()}Builder.of()));
                |}
                """ else ""}
            """.trimMargin()
        } else {
            var propertyName = property.name
            var checkedPropertyType = if (propertyType.isInlineType) propertyType.type else propertyType
            if(SourceVersion.isKeyword(propertyName)) {
                propertyName = "_$propertyName"
            }
            """
                |${if (checkedPropertyType is ObjectType && !checkedPropertyType.isAbstract() && propType.simpleName() != JavaBaseTypes.objectType.simpleName()) """
                |/**
                |${propertyType.toComment(" * set the value to the $propertyName using the builder function")}
                | * @param builder function to build the $propertyName value
                | * @return Builder
                | */
                |${property.deprecationAnnotation()}
                |public ${type.simpleClassName}Builder $propertyName(Function<${propType.fullClassName()}Builder, ${propType.fullClassName()}Builder> builder) {
                |    this.$propertyName = builder.apply(${propType.fullClassName()}Builder.of()).build();
                |    return this;
                |}
                |
                |/**
                |${propertyType.toComment(" * set the value to the $propertyName using the builder function")}
                | * @param builder function to build the $propertyName value
                | * @return Builder
                | */
                |${property.deprecationAnnotation()}
                |public ${type.simpleClassName}Builder with${propertyName.firstUpperCase()}(Function<${propType.fullClassName()}Builder, ${propType.fullClassName()}> builder) {
                |    this.$propertyName = builder.apply(${propType.fullClassName()}Builder.of());
                |    return this;
                |}
                """ else ""}
                |/**
                |${propertyType.toComment(" * set the value to the $propertyName")}
                | * @param $propertyName value to be set
                | * @return Builder
                | */
                |${property.deprecationAnnotation()}
                |public ${type.simpleClassName}Builder $propertyName(${if (!property.required) "@Nullable" else ""} final ${propType.fullClassName()} $propertyName) {
                |    this.$propertyName = $propertyName;
                |    return this;
                |}
                |
                |${if (checkedPropertyType is ObjectType && checkedPropertyType.isAbstract() && checkedPropertyType.discriminator != null) """
                |/**
                |${propertyType.toComment(" * set the value to the $propertyName using the builder function")}
                | * @param builder function to build the $propertyName value
                | * @return Builder
                | */
                |${property.deprecationAnnotation()}
                |public ${type.simpleClassName}Builder $propertyName(Function<${propType.fullClassName()}Builder, Builder> builder) {
                |    this.$propertyName = builder.apply(${propType.fullClassName()}Builder.of()).build();
                |    return this;
                |}
                """ else ""}
            """.trimMargin()
        }
    }

    private fun ObjectType.getters() : String {
        return this.allProperties
                .filterNot { it.deprecated() }
                .filter { it.name != this.discriminator() }
                .map { it.getter() }
                .joinToString(separator = "\n\n")
    }

    private fun Property.getter() : String {
        val propertyType = this.type;
        val vrapType = this.type.toVrapType()
        return if(this.isPatternProperty()){
            """
                |/**
                |${propertyType.toComment(" * values of pattern properties")}
                | * @return pattern properties
                | */
                |${this.deprecationAnnotation()}
                |${if(!this.required) "@Nullable" else ""}
                |public Map getValues(){
                |    return this.values;
                |}
            """.escapeAll().trimMargin().keepIndentation()
        } else if(this.name.equals("interface")) {
            """
                |/**
                |${propertyType.toComment(" * value of interface")}
                | * @return interface
                | */
                | 
                |${this.deprecationAnnotation()}
                |${if (!this.required) "@Nullable" else ""}
                |public ${vrapType.fullClassName()} getInterface(){
                |    return this._interface;
                |}
            """.escapeAll().trimMargin().keepIndentation()
        }else{
            """
                |/**
                |${propertyType.toComment(" * value of ${this.name}}")}
                | * @return ${this.name}
                | */
                |${this.deprecationAnnotation()}
                |${if(!this.required) "@Nullable" else ""}
                |public ${vrapType.fullClassName()} get${this.name.firstUpperCase()}(){
                |    return this.${this.name};
                |}
            """.escapeAll().trimMargin().keepIndentation()
        }
    }

    private fun ObjectType.requiredChecks() : String {
        val vrapType = vrapTypeProvider.doSwitch(this).toJavaVType() as VrapObjectType
        return this.allProperties
            .asSequence()
            .filterNot { it.deprecated() }
            .filter { it.name != this.discriminator() }
            .filterNot { it.isPatternProperty() }
            .filter { it.required }
            .map {
                if(it.name.equals("interface")) {
                    "Objects.requireNonNull(_interface, ${vrapType.simpleClassName}.class + \": interface is missing\");"
                } else {
                    "Objects.requireNonNull(${it.name}, ${vrapType.simpleClassName}.class + \": ${it.name} is missing\");"
                }
            }
            .joinToString(separator = "\n")
    }

    private fun ObjectType.buildMethodBody() : String {
        val vrapType = vrapTypeProvider.doSwitch(this).toJavaVType() as VrapObjectType
        val constructorArguments = this.allProperties
            .filterNot { it.deprecated() }
            .filter { it.name != this.discriminator() }
            .joinToString(separator = ", ") {
                if (it.isPatternProperty()) {
                    "values"
                } else if (it.name.equals("interface")) {
                    "_interface"
                } else {
                    it.name
                }
            }
        return "return new ${vrapType.simpleClassName}Impl($constructorArguments);"
    }

    private fun ObjectType.staticOfMethod() : String {
        val vrapType = vrapTypeProvider.doSwitch(this).toJavaVType() as VrapObjectType

        return """
            |/**
            | * factory method for an instance of ${vrapType.simpleClassName}Builder
            | * @return builder 
            | */
            |public static ${vrapType.simpleClassName}Builder of() {
            |    return new ${vrapType.simpleClassName}Builder();
            |}
        """.trimMargin().keepIndentation()
    }

    private fun ObjectType.templateMethod(): String {
        val vrapType = vrapTypeProvider.doSwitch(this).toJavaVType() as VrapObjectType
        val fieldsAssignment : String = this.allProperties
            .filterNot { it.deprecated() }
            .filter { it.name != this.discriminator() }
            .joinToString(separator = "\n") {
                if (it.isPatternProperty()) {
                    "builder.values = template.values();"
                } else if (it.name.equals("interface")) {
                    "builder._interface = template.getInterface();"
                } else {
                    "builder.${it.name} = template.get${it.name.upperCamelCase()}();"
                }
            }

        return """
            |/**
            | * create builder for ${vrapType.simpleClassName} instance
            | * @param template instance with prefilled values for the builder
            | * @return builder
            | */
            |public static ${vrapType.simpleClassName}Builder of(final ${vrapType.simpleClassName} template) {
            |    ${vrapType.simpleClassName}Builder builder = new ${vrapType.simpleClassName}Builder();
            |    <$fieldsAssignment>
            |    return builder;
            |}
        """.escapeAll().trimMargin().keepIndentation()
    }

    private fun ObjectType.markDeprecated() : Boolean {
        val anno = this.getAnnotation("markDeprecated")
        return (anno != null && (anno.value as BooleanInstance).value)
    }

    private fun ObjectType.deprecated() : Boolean {
        val anno = this.getAnnotation("deprecated")
        return (anno != null && (anno.value as BooleanInstance).value)
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy