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

software.amazon.awssdk.codegen.poet.model.ModelBuilderSpecs Maven / Gradle / Ivy

/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 *  http://aws.amazon.com/apache2.0
 *
 * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.codegen.poet.model;

import static javax.lang.model.element.Modifier.FINAL;
import static javax.lang.model.element.Modifier.PRIVATE;
import static javax.lang.model.element.Modifier.PUBLIC;
import static software.amazon.awssdk.codegen.poet.model.DeprecationUtils.checkDeprecated;

import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import com.squareup.javapoet.WildcardTypeName;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import javax.lang.model.element.Modifier;
import software.amazon.awssdk.awscore.AwsRequestOverrideConfiguration;
import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel;
import software.amazon.awssdk.codegen.model.intermediate.MemberModel;
import software.amazon.awssdk.codegen.model.intermediate.ShapeModel;
import software.amazon.awssdk.codegen.model.intermediate.ShapeType;
import software.amazon.awssdk.codegen.poet.PoetExtension;
import software.amazon.awssdk.core.SdkField;
import software.amazon.awssdk.core.SdkPojo;
import software.amazon.awssdk.core.util.DefaultSdkAutoConstructList;
import software.amazon.awssdk.core.util.DefaultSdkAutoConstructMap;
import software.amazon.awssdk.core.util.SdkAutoConstructList;
import software.amazon.awssdk.core.util.SdkAutoConstructMap;
import software.amazon.awssdk.utils.Validate;
import software.amazon.awssdk.utils.builder.CopyableBuilder;

/**
 * Provides the Poet specs for model class builders.
 */
class ModelBuilderSpecs {
    private final IntermediateModel intermediateModel;
    private final ShapeModel shapeModel;
    private final TypeProvider typeProvider;
    private final PoetExtension poetExtensions;
    private final AccessorsFactory accessorsFactory;
    private final ShapeModelSpec shapeModelSpec;

    ModelBuilderSpecs(IntermediateModel intermediateModel,
                      ShapeModel shapeModel,
                      TypeProvider typeProvider) {
        this.intermediateModel = intermediateModel;
        this.shapeModel = shapeModel;
        this.typeProvider = typeProvider;
        this.poetExtensions = new PoetExtension(this.intermediateModel);
        this.accessorsFactory = new AccessorsFactory(this.shapeModel, this.intermediateModel, this.typeProvider, poetExtensions);
        this.shapeModelSpec = new ShapeModelSpec(shapeModel, typeProvider, poetExtensions, intermediateModel);
    }

    public ClassName builderInterfaceName() {
        return classToBuild().nestedClass("Builder");
    }

    public ClassName builderImplName() {
        return classToBuild().nestedClass("BuilderImpl");
    }

    public TypeSpec builderInterface() {
        TypeSpec.Builder builder = TypeSpec.interfaceBuilder(builderInterfaceName())
                .addSuperinterfaces(builderSuperInterfaces())
                .addModifiers(PUBLIC);

        shapeModel.getNonStreamingMembers()
                  .forEach(m -> {
                      builder.addMethods(
                          checkDeprecated(m, accessorsFactory.fluentSetterDeclarations(m, builderInterfaceName())));
                      builder.addMethods(
                          checkDeprecated(m, accessorsFactory.convenienceSetterDeclarations(m, builderInterfaceName())));
                  });

        if (isException()) {
            builder.addSuperinterface(parentExceptionBuilder().nestedClass("Builder"));
            builder.addMethods(ExceptionProperties.builderInterfaceMethods(builderInterfaceName()));
        }

        if (isRequest()) {
            builder.addMethod(MethodSpec.methodBuilder("overrideConfiguration")
                    .returns(builderInterfaceName())
                    .addAnnotation(Override.class)
                    .addParameter(AwsRequestOverrideConfiguration.class, "overrideConfiguration")
                    .addModifiers(PUBLIC, Modifier.ABSTRACT)
                    .build());

            builder.addMethod(MethodSpec.methodBuilder("overrideConfiguration")
                    .addAnnotation(Override.class)
                    .returns(builderInterfaceName())
                    .addParameter(ParameterizedTypeName.get(Consumer.class, AwsRequestOverrideConfiguration.Builder.class),
                            "builderConsumer")
                    .addModifiers(PUBLIC, Modifier.ABSTRACT)
                    .build());
        }

        return builder.build();
    }

    public TypeSpec beanStyleBuilder() {
        TypeSpec.Builder builderClassBuilder = TypeSpec.classBuilder(builderImplName())
                .addSuperinterface(builderInterfaceName())
                // TODO: Uncomment this once property shadowing is fixed
                //.addSuperinterface(copyableBuilderSuperInterface())
                .superclass(builderImplSuperClass())
                .addModifiers(Modifier.STATIC);

        if (!isEvent()) {
            builderClassBuilder.addModifiers(Modifier.FINAL);
        } else {
            builderClassBuilder.addModifiers(Modifier.PROTECTED);
        }

        if (isException()) {
            builderClassBuilder.superclass(parentExceptionBuilder().nestedClass("BuilderImpl"));
        }

        builderClassBuilder.addFields(fields());
        builderClassBuilder.addMethod(noargConstructor());
        builderClassBuilder.addMethod(modelCopyConstructor());
        builderClassBuilder.addMethods(accessors());
        builderClassBuilder.addMethod(buildMethod());
        builderClassBuilder.addMethod(sdkFieldsMethod());
        builderClassBuilder.addMethod(sdkFieldNameToFieldMethod());

        if (shapeModel.isUnion()) {
            builderClassBuilder.addMethod(handleUnionValueChangeMethod());
        }

        return builderClassBuilder.build();
    }

    private ClassName parentExceptionBuilder() {
        String customExceptionBase = intermediateModel.getCustomizationConfig()
                .getSdkModeledExceptionBaseClassName();
        if (customExceptionBase != null) {
            return poetExtensions.getModelClass(customExceptionBase);
        }
        return poetExtensions.getModelClass(intermediateModel.getSdkModeledExceptionBaseClassName());
    }

    private MethodSpec sdkFieldsMethod() {
        ParameterizedTypeName sdkFieldType = ParameterizedTypeName.get(ClassName.get(SdkField.class),
                                                                       WildcardTypeName.subtypeOf(ClassName.get(Object.class)));
        return MethodSpec.methodBuilder("sdkFields")
                         .addModifiers(PUBLIC)
                         .addAnnotation(Override.class)
                         .returns(ParameterizedTypeName.get(ClassName.get(List.class), sdkFieldType))
                         .addCode("return SDK_FIELDS;")
                         .build();
    }

    private MethodSpec sdkFieldNameToFieldMethod() {
        ParameterizedTypeName sdkFieldType = ParameterizedTypeName.get(ClassName.get(SdkField.class),
                                                                       WildcardTypeName.subtypeOf(ClassName.get(Object.class)));
        return MethodSpec.methodBuilder("sdkFieldNameToField")
                         .addModifiers(PUBLIC)
                         .addAnnotation(Override.class)
                         .returns(ParameterizedTypeName.get(ClassName.get(Map.class), ClassName.get(String.class), sdkFieldType))
                         .addCode("return SDK_NAME_TO_FIELD;")
                         .build();
    }

    private TypeName builderImplSuperClass() {
        if (isRequest()) {
            return new AwsServiceBaseRequestSpec(intermediateModel).className().nestedClass("BuilderImpl");
        }

        if (isResponse()) {
            return new AwsServiceBaseResponseSpec(intermediateModel).className().nestedClass("BuilderImpl");
        }

        return ClassName.OBJECT;
    }

    private List fields() {
        List fields = new ArrayList<>();

        for (MemberModel member : shapeModel.getNonStreamingMembers()) {
            FieldSpec fieldSpec = typeProvider.asField(member, Modifier.PRIVATE);
            if (member.isList()) {
                fieldSpec = fieldSpec.toBuilder()
                                     .initializer("$T.getInstance()", DefaultSdkAutoConstructList.class)
                                     .build();
            } else if (member.isMap()) {
                fieldSpec = fieldSpec.toBuilder()
                                     .initializer("$T.getInstance()", DefaultSdkAutoConstructMap.class)
                                     .build();
            }

            fields.add(fieldSpec);
        }

        if (shapeModel.isUnion()) {
            ClassName unionType = shapeModelSpec.className().nestedClass("Type");
            fields.add(FieldSpec.builder(unionType, "type", PRIVATE)
                                .initializer("$T.UNKNOWN_TO_SDK_VERSION", unionType)
                                .build());
            fields.add(FieldSpec.builder(ParameterizedTypeName.get(ClassName.get(Set.class), unionType), "setTypes", PRIVATE)
                                .initializer("$T.noneOf($T.class)", EnumSet.class, unionType)
                                .build());
        }

        return fields;
    }

    private MethodSpec noargConstructor() {
        Modifier modifier = isEvent() ? Modifier.PROTECTED : Modifier.PRIVATE;
        MethodSpec.Builder ctorBuilder = MethodSpec.constructorBuilder()
                .addModifiers(modifier);
        return ctorBuilder.build();
    }

    private MethodSpec modelCopyConstructor() {
        Modifier modifier = isEvent() ? Modifier.PROTECTED : Modifier.PRIVATE;
        MethodSpec.Builder copyBuilderCtor = MethodSpec.constructorBuilder()
                .addModifiers(modifier)
                .addParameter(classToBuild(), "model");

        if (isRequest() || isResponse() || isException()) {
            copyBuilderCtor.addCode("super(model);");
        }

        shapeModel.getNonStreamingMembers().forEach(m -> {
            String name = m.getVariable().getVariableName();
            copyBuilderCtor.addStatement("$N(model.$N)", m.getFluentSetterMethodName(), name);
        });

        return copyBuilderCtor.build();
    }

    private List accessors() {
        List accessors = new ArrayList<>();
        shapeModel.getNonStreamingMembers()
                  .forEach(m -> {
                      accessors.add(checkDeprecated(m, accessorsFactory.beanStyleGetter(m)));
                      accessors.addAll(checkDeprecated(m, accessorsFactory.beanStyleSetters(m)));
                      accessors.addAll(checkDeprecated(m, accessorsFactory.fluentSetters(m, builderInterfaceName())));
                      accessors.addAll(checkDeprecated(m, accessorsFactory.convenienceSetters(m, builderInterfaceName())));
                  });

        if (isException()) {
            accessors.addAll(ExceptionProperties.builderImplMethods(builderImplName()));
        }

        if (isRequest()) {
            accessors.add(MethodSpec.methodBuilder("overrideConfiguration")
                    .addAnnotation(Override.class)
                    .returns(builderInterfaceName())
                    .addParameter(AwsRequestOverrideConfiguration.class, "overrideConfiguration")
                    .addModifiers(PUBLIC)
                    .addStatement("super.overrideConfiguration(overrideConfiguration)")
                    .addStatement("return this")
                    .build());

            accessors.add(MethodSpec.methodBuilder("overrideConfiguration")
                    .addAnnotation(Override.class)
                    .returns(builderInterfaceName())
                    .addParameter(ParameterizedTypeName.get(Consumer.class, AwsRequestOverrideConfiguration.Builder.class),
                            "builderConsumer")
                    .addModifiers(PUBLIC)
                    .addStatement("super.overrideConfiguration(builderConsumer)")
                    .addStatement("return this")
                    .build());
        }

        return accessors;
    }

    private MethodSpec buildMethod() {
        return MethodSpec.methodBuilder("build")
                .addAnnotation(Override.class)
                .addModifiers(PUBLIC)
                .returns(classToBuild())
                .addStatement("return new $T(this)", classToBuild())
                .build();
    }

    private ClassName classToBuild() {
        return shapeModelSpec.className();
    }

    private boolean isException() {
        return shapeModel.getShapeType() == ShapeType.Exception;
    }

    private boolean isRequest() {
        return shapeModel.getShapeType() == ShapeType.Request;
    }

    private boolean isResponse() {
        return shapeModel.getShapeType() == ShapeType.Response;
    }

    private boolean isEvent() {
        return shapeModel.isEvent();
    }

    private List builderSuperInterfaces() {
        List superInterfaces = new ArrayList<>();
        if (isRequest()) {
            superInterfaces.add(new AwsServiceBaseRequestSpec(intermediateModel).className().nestedClass("Builder"));
        }
        if (isResponse()) {
            superInterfaces.add(new AwsServiceBaseResponseSpec(intermediateModel).className().nestedClass("Builder"));
        }
        superInterfaces.add(ClassName.get(SdkPojo.class));
        superInterfaces.add(ParameterizedTypeName.get(ClassName.get(CopyableBuilder.class),
                classToBuild().nestedClass("Builder"), classToBuild()));
        return superInterfaces;
    }

    public TypeSpec unionTypeClass() {
        Validate.isTrue(shapeModel.isUnion(), "%s was not a union.", shapeModel.getShapeName());

        TypeSpec.Builder type = TypeSpec.enumBuilder("Type")
                                        .addJavadoc("@see $L#type()", shapeModel.getShapeName())
                                        .addModifiers(PUBLIC);

        for (MemberModel member : shapeModel.getMembers()) {
            type.addEnumConstant(member.getUnionEnumTypeName());
        }

        type.addEnumConstant("UNKNOWN_TO_SDK_VERSION");

        return type.build();
    }

    private MethodSpec handleUnionValueChangeMethod() {
        CodeBlock body =
            CodeBlock.builder()
                     .beginControlFlow("if (this.type == type || oldValue == newValue)")
                     .addStatement("return")
                     .endControlFlow()
                     .beginControlFlow("if (newValue == null || newValue instanceof $T || newValue instanceof $T)",
                                       SdkAutoConstructList.class, SdkAutoConstructMap.class)
                     .addStatement("setTypes.remove(type)")
                     .nextControlFlow("else if (oldValue == null || oldValue instanceof $T || oldValue instanceof $T)",
                                      SdkAutoConstructList.class, SdkAutoConstructMap.class)
                     .addStatement("setTypes.add(type)")
                     .endControlFlow()
                     .beginControlFlow("if (setTypes.size() == 1)")
                     .addStatement("this.type = setTypes.iterator().next()")
                     .nextControlFlow("else if (setTypes.isEmpty())")
                     .addStatement("this.type = Type.UNKNOWN_TO_SDK_VERSION")
                     .nextControlFlow("else")
                     .addStatement("this.type = null")
                     .endControlFlow()
                     .build();

        return MethodSpec.methodBuilder("handleUnionValueChange")
                         .addModifiers(PRIVATE, FINAL)
                         .returns(void.class)
                         .addParameter(shapeModelSpec.className().nestedClass("Type"), "type")
                         .addParameter(Object.class, "oldValue")
                         .addParameter(Object.class, "newValue")
                         .addCode(body)
                         .build();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy