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

software.amazon.awssdk.codegen.poet.model.AwsServiceModel 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 java.util.Collections.emptyList;
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 javax.lang.model.element.Modifier.STATIC;
import static software.amazon.awssdk.codegen.internal.Utils.capitalize;
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.TypeVariableName;
import com.squareup.javapoet.WildcardTypeName;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.lang.model.element.Modifier;
import software.amazon.awssdk.annotations.SdkPublicApi;
import software.amazon.awssdk.codegen.docs.DocumentationBuilder;
import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel;
import software.amazon.awssdk.codegen.model.intermediate.MemberModel;
import software.amazon.awssdk.codegen.model.intermediate.OperationModel;
import software.amazon.awssdk.codegen.model.intermediate.ShapeModel;
import software.amazon.awssdk.codegen.model.intermediate.ShapeType;
import software.amazon.awssdk.codegen.model.intermediate.VariableModel;
import software.amazon.awssdk.codegen.naming.NamingStrategy;
import software.amazon.awssdk.codegen.poet.ClassSpec;
import software.amazon.awssdk.codegen.poet.PoetExtension;
import software.amazon.awssdk.codegen.poet.PoetUtils;
import software.amazon.awssdk.codegen.poet.eventstream.EventStreamUtils;
import software.amazon.awssdk.codegen.poet.model.TypeProvider.TypeNameOptions;
import software.amazon.awssdk.core.SdkField;
import software.amazon.awssdk.core.SdkPojo;
import software.amazon.awssdk.utils.StringUtils;
import software.amazon.awssdk.utils.Validate;
import software.amazon.awssdk.utils.builder.ToCopyableBuilder;

/**
 * Provides the Poet specs for AWS Service models.
 */
public class AwsServiceModel implements ClassSpec {

    private final IntermediateModel intermediateModel;
    private final ShapeModel shapeModel;
    private final PoetExtension poetExtensions;
    private final TypeProvider typeProvider;
    private final ShapeModelSpec shapeModelSpec;
    private final ModelBuilderSpecs modelBuilderSpecs;
    private final ServiceModelCopiers serviceModelCopiers;
    private final ModelMethodOverrides modelMethodOverrides;

    public AwsServiceModel(IntermediateModel intermediateModel, ShapeModel shapeModel) {
        this.intermediateModel = intermediateModel;
        this.shapeModel = shapeModel;
        this.poetExtensions = new PoetExtension(intermediateModel);
        this.typeProvider = new TypeProvider(intermediateModel);
        this.shapeModelSpec = new ShapeModelSpec(this.shapeModel,
                                                 typeProvider,
                                                 poetExtensions,
                                                 intermediateModel);
        this.modelBuilderSpecs = resolveBuilderSpecs();
        this.serviceModelCopiers = new ServiceModelCopiers(this.intermediateModel);
        this.modelMethodOverrides = new ModelMethodOverrides(className(), this.poetExtensions);
    }

    @Override
    public TypeSpec poetSpec() {
        if (shapeModel.isEventStream()) {
            return eventStreamInterfaceSpec();
        }
        List fields = shapeModelSpec.fields();

        TypeSpec.Builder specBuilder = TypeSpec.classBuilder(className())
                                               .addModifiers(PUBLIC)
                                               .addAnnotation(PoetUtils.generatedAnnotation())
                                               .addSuperinterfaces(modelSuperInterfaces())
                                               .superclass(modelSuperClass())
                                               .addMethods(modelClassMethods())
                                               .addFields(fields)
                                               .addFields(shapeModelSpec.staticFields())
                                               .addMethod(addModifier(sdkFieldsMethod(), FINAL))
                                               .addMethod(addModifier(sdkFieldNameToFieldMethod(), FINAL))
                                               .addTypes(nestedModelClassTypes());

        shapeModelSpec.additionalMethods().forEach(specBuilder::addMethod);

        if (shapeModel.isUnion()) {
            specBuilder.addField(unionTypeField());
        }

        if (!isEvent()) {
            specBuilder.addModifiers(Modifier.FINAL);
        }

        // Add serializable version UID for model and exceptions.
        if (shapeModel.getShapeType() == ShapeType.Model || shapeModel.getShapeType() == ShapeType.Exception) {
            specBuilder.addField(FieldSpec.builder(long.class, "serialVersionUID",
                                                   Modifier.PRIVATE, STATIC, Modifier.FINAL)
                                          .initializer("1L")
                                          .build());
        }

        if (!fields.isEmpty()) {
            specBuilder
                .addMethod(getterCreator())
                .addMethod(setterCreator());
        }

        if (this.shapeModel.isEvent()) {
            addEventSupport(specBuilder);
        }

        if (this.shapeModel.getDocumentation() != null) {
            specBuilder.addJavadoc("$L", this.shapeModel.getDocumentation());
        }

        return specBuilder.build();
    }

    private ModelBuilderSpecs resolveBuilderSpecs() {
        return new ModelBuilderSpecs(intermediateModel, shapeModel, typeProvider);
    }

    private TypeSpec eventStreamInterfaceSpec() {
        Collection opModels = EventStreamUtils.findOperationsWithEventStream(intermediateModel,
                shapeModel);

        Collection outputOperations = findOutputEventStreamOperations(opModels, shapeModel);

        EventStreamSpecHelper helper = new EventStreamSpecHelper(shapeModel, intermediateModel);

        ClassName modelClass = poetExtensions.getModelClassFromShape(shapeModel);

        TypeSpec.Builder builder =
                PoetUtils.createInterfaceBuilder(modelClass)
                         .addAnnotation(SdkPublicApi.class)
                         .addMethods(eventStreamInterfaceEventBuilderMethods())
                         .addType(helper.eventTypeEnumSpec());


        ClassName eventTypeEnum = helper.eventTypeEnumClassName();
        builder.addMethod(MethodSpec.methodBuilder("sdkEventType")
                .addModifiers(Modifier.PUBLIC, Modifier.DEFAULT)
                .returns(eventTypeEnum)
                .addJavadoc("The type of this event. Corresponds to the {@code :event-type} header on the Message.")
                .addStatement("return $T.UNKNOWN_TO_SDK_VERSION", eventTypeEnum)
                .build());

        if (!outputOperations.isEmpty()) {
            CodeBlock unknownInitializer = buildUnknownEventStreamInitializer(outputOperations,
                    modelClass);

            builder.addSuperinterface(ClassName.get(SdkPojo.class))
                   .addJavadoc("Base interface for all event types in $L.", shapeModel.getShapeName())
                    .addField(FieldSpec.builder(modelClass, "UNKNOWN")
                                    .addModifiers(PUBLIC, STATIC, Modifier.FINAL)
                                    .initializer(unknownInitializer)
                                    .addJavadoc("Special type of {@link $T} for unknown types of events that this "
                                            + "version of the SDK does not know about", modelClass)
                                    .build());

            for (OperationModel opModel : outputOperations) {
                ClassName responseHandlerClass = poetExtensions.eventStreamResponseHandlerType(opModel);
                builder.addMethod(acceptMethodSpec(modelClass, responseHandlerClass)
                        .addModifiers(Modifier.ABSTRACT)
                        .build());
            }

            return builder.build();

        } else if (hasInputStreamOperations(opModels, shapeModel)) {
            return builder.addJavadoc("Base interface for all event types in $L.", shapeModel.getShapeName())
                          .build();
        }

        throw new IllegalArgumentException(shapeModel.getShapeName() + " event stream shape is not found "
                + "in any request or response shape");
    }

    private void addEventSupport(TypeSpec.Builder specBuilder) {
        EventStreamUtils.getBaseEventStreamShapes(intermediateModel, shapeModel)
                .forEach(eventStream -> addEventSupport(specBuilder, eventStream));
    }

    private void addEventSupport(TypeSpec.Builder specBuilder, ShapeModel eventStream) {
        ClassName eventStreamClassName = poetExtensions.getModelClassFromShape(eventStream);
        Collection opModels = EventStreamUtils.findOperationsWithEventStream(intermediateModel,
                                                                                             eventStream);

        Collection outputOperations = findOutputEventStreamOperations(opModels, eventStream);

        boolean onOutput = !outputOperations.isEmpty();
        boolean onInput = hasInputStreamOperations(opModels, eventStream);

        if (!onOutput && !onInput) {
            throw new IllegalArgumentException(shapeModel.getC2jName() + " event shape is not a member in any "
                    + "request or response event shape");
        }

        EventStreamSpecHelper helper = new EventStreamSpecHelper(eventStream, intermediateModel);

        specBuilder.addSuperinterface(eventStreamClassName);

        boolean usesLegacyScheme = useLegacyEventGenerationScheme(eventStream);
        Optional legacyEvent = findLegacyGenerationEventWithShape(eventStream);

        if (usesLegacyScheme && legacyEvent.isPresent()) {
            NamingStrategy namingStrategy = intermediateModel.getNamingStrategy();
            ClassName eventTypeEnum = helper.eventTypeEnumClassName();
            specBuilder.addMethod(MethodSpec.methodBuilder("sdkEventType")
                       .addAnnotation(Override.class)
                       .addModifiers(PUBLIC)
                       .returns(eventTypeEnum)
                       .addStatement("return $T.$N",
                                     eventTypeEnum,
                                     namingStrategy.getEnumValueName(legacyEvent.get().getName()))
                       .build());
        }

        if (onOutput) {
            ClassName modelClass = poetExtensions.getModelClass(shapeModel.getShapeName());
            for (OperationModel opModel : outputOperations) {
                ClassName responseHandlerClass = poetExtensions.eventStreamResponseHandlerType(opModel);

                MethodSpec.Builder acceptMethodSpec = acceptMethodSpec(modelClass, responseHandlerClass)
                        .addAnnotation(Override.class);

                if (usesLegacyScheme) {
                    acceptMethodSpec.addStatement("visitor.visit(this)");
                } else {
                    // The class that represents the event type will be
                    // responsible for implementing this
                    acceptMethodSpec.addStatement("throw new $T()", UnsupportedOperationException.class);
                }

                specBuilder.addMethod(acceptMethodSpec.build());
            }
        }
    }

    private boolean hasInputStreamOperations(Collection opModels, ShapeModel eventStream) {
        return opModels.stream()
                       .anyMatch(op -> EventStreamUtils.doesShapeContainsEventStream(op.getInputShape(), eventStream));
    }

    private List findOutputEventStreamOperations(Collection opModels,
                                                                 ShapeModel eventStream) {
        return opModels
            .stream()
            .filter(opModel -> EventStreamUtils.doesShapeContainsEventStream(opModel.getOutputShape(), eventStream))
            .collect(Collectors.toList());
    }

    private CodeBlock buildUnknownEventStreamInitializer(Collection outputOperations,
                                                         ClassName eventStreamModelClass) {
        CodeBlock.Builder builder = CodeBlock.builder()
                                             .add("new $T() {\n"
                                                  + "        @Override\n"
                                                  + "        public $T<$T> sdkFields() {\n"
                                                  + "            return $T.emptyList();\n"
                                                  + "        }\n",
                                                  eventStreamModelClass, List.class, SdkField.class,
                                                  Collections.class
                                             );

        for (OperationModel opModel : outputOperations) {
            ClassName responseHandlerClass = poetExtensions.eventStreamResponseHandlerType(opModel);
            builder.add("        @Override\n"
                      + "        public void accept($T.Visitor visitor) {"
                      + "            \nvisitor.visitDefault(this);\n"
                      + "        }\n", responseHandlerClass);
        }

        builder.add("    }\n");

        return builder.build();
    }

    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 MethodSpec getterCreator() {
        TypeVariableName t = TypeVariableName.get("T");
        return MethodSpec.methodBuilder("getter")
                         .addTypeVariable(t)
                         .addModifiers(Modifier.PRIVATE, STATIC)
                         .addParameter(ParameterizedTypeName.get(ClassName.get(Function.class),
                                                                 className(), t),
                                       "g")
                         .returns(ParameterizedTypeName.get(ClassName.get(Function.class),
                                                            ClassName.get(Object.class), t))
                         .addStatement("return obj -> g.apply(($T) obj)", className())
                         .build();
    }

    private MethodSpec setterCreator() {
        TypeVariableName t = TypeVariableName.get("T");
        return MethodSpec.methodBuilder("setter")
                         .addTypeVariable(t)
                         .addModifiers(Modifier.PRIVATE, STATIC)
                         .addParameter(ParameterizedTypeName.get(ClassName.get(BiConsumer.class),
                                                                 builderClassName(),
                                                                 t),
                                       "s")
                         .returns(ParameterizedTypeName.get(ClassName.get(BiConsumer.class),
                                                            ClassName.get(Object.class), t))
                         .addStatement("return (obj, val) -> s.accept(($T) obj, val)", builderClassName())
                         .build();
    }

    private MethodSpec.Builder acceptMethodSpec(ClassName modelClass, ClassName responseHandlerClass) {
        return MethodSpec.methodBuilder("accept")
                         .addModifiers(PUBLIC)
                         .addJavadoc(new DocumentationBuilder()
                                         .description("Calls the appropriate visit method depending on "
                                                      + "the subtype of {@link $T}.")
                                         .param("visitor", "Visitor to invoke.")
                                         .build(), modelClass)
                         .addParameter(responseHandlerClass
                                           .nestedClass("Visitor"), "visitor");
    }

    @Override
    public ClassName className() {
        return shapeModelSpec.className();
    }

    private ClassName builderClassName() {
        return className().nestedClass("Builder");
    }

    private ClassName unionTypeClassName() {
        return className().nestedClass("Type");
    }

    private List modelSuperInterfaces() {
        List interfaces = new ArrayList<>();


        switch (shapeModel.getShapeType()) {
            case Model:
                interfaces.add(ClassName.get(SdkPojo.class));
                interfaces.add(ClassName.get(Serializable.class));
                interfaces.add(toCopyableBuilderInterface());
                break;
            case Exception:
            case Request:
            case Response:
                interfaces.add(toCopyableBuilderInterface());
                break;
            default:
                break;
        }

        return interfaces;
    }

    private TypeName modelSuperClass() {
        switch (shapeModel.getShapeType()) {
            case Request:
                return requestBaseClass();
            case Response:
                return responseBaseClass();
            case Exception:
                return exceptionBaseClass();
            default:
                return ClassName.OBJECT;
        }
    }

    private TypeName requestBaseClass() {
        return new AwsServiceBaseRequestSpec(intermediateModel).className();
    }

    private TypeName responseBaseClass() {
        return new AwsServiceBaseResponseSpec(intermediateModel).className();
    }

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

    private TypeName toCopyableBuilderInterface() {
        return ParameterizedTypeName.get(ClassName.get(ToCopyableBuilder.class),
                                         className().nestedClass("Builder"),
                                         className());
    }

    private List modelClassMethods() {
        List methodSpecs = new ArrayList<>();

        switch (shapeModel.getShapeType()) {
            case Exception:
                methodSpecs.add(exceptionConstructor());
                methodSpecs.add(toBuilderMethod());
                methodSpecs.add(builderMethod());
                methodSpecs.add(serializableBuilderClass());
                methodSpecs.addAll(memberGetters());
                methodSpecs.addAll(retryableOverrides());
                break;
            default:
                methodSpecs.addAll(addModifier(memberGetters(), FINAL));
                methodSpecs.add(constructor());
                methodSpecs.add(toBuilderMethod());
                methodSpecs.add(builderMethod());
                methodSpecs.add(serializableBuilderClass());
                methodSpecs.add(addModifier(modelMethodOverrides.hashCodeMethod(shapeModel), FINAL));
                methodSpecs.add(addModifier(modelMethodOverrides.equalsMethod(shapeModel), FINAL));
                methodSpecs.add(addModifier(modelMethodOverrides.equalsBySdkFieldsMethod(shapeModel), FINAL));
                methodSpecs.add(addModifier(modelMethodOverrides.toStringMethod(shapeModel), FINAL));
                methodSpecs.add(getValueForField());
                methodSpecs.addAll(unionMembers());
                break;
        }

        if (isEvent()) {
            methodSpecs.add(copyMethod());
        }

        return methodSpecs;
    }

    private MethodSpec getValueForField() {
        MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("getValueForField")
                                                     .addModifiers(PUBLIC, FINAL)
                                                     .addTypeVariable(TypeVariableName.get("T"))
                                                     .returns(ParameterizedTypeName.get(ClassName.get(Optional.class),
                                                                                        TypeVariableName.get("T")))
                                                     .addParameter(String.class, "fieldName")
                                                     .addParameter(ParameterizedTypeName.get(ClassName.get(Class.class),
                                                                                             TypeVariableName.get("T")),
                                                                   "clazz");

        if (shapeModel.getNonStreamingMembers().isEmpty()) {
            methodBuilder.addStatement("return $T.empty()", Optional.class);
            return methodBuilder.build();
        }


        methodBuilder.beginControlFlow("switch ($L)", "fieldName");

        shapeModel.getNonStreamingMembers().forEach(m -> addCasesForMember(methodBuilder, m));

        methodBuilder.addCode("default:");
        methodBuilder.addStatement("return $T.empty()", Optional.class);
        methodBuilder.endControlFlow();

        return methodBuilder.build();
    }

    private FieldSpec unionTypeField() {
        return FieldSpec.builder(unionTypeClassName(), "type", PRIVATE, FINAL).build();
    }

    private Collection unionMembers() {
        if (!shapeModel.isUnion()) {
            return emptyList();
        }

        Validate.isFalse(shapeModel.isEvent(), "Event shape %s must not be a union", shapeModel.getShapeName());
        Validate.isFalse(shapeModel.isEventStream(), "Event stream shape %s must not be a union", shapeModel.getShapeName());
        Validate.isFalse(shapeModel.isDocument(), "Document shape %s must not be a union", shapeModel.getShapeName());
        Validate.isFalse(isRequest() && isResponse(), "Input or output shape %s must not be a union", shapeModel.getShapeName());

        List unionMembers = new ArrayList<>();
        unionMembers.addAll(unionConstructors());
        unionMembers.add(unionTypeMethod());
        unionMembers.addAll(unionAcceptMethods());
        return unionMembers;
    }

    private Collection unionConstructors() {
        return shapeModel.getMembers().stream()
                         .flatMap(this::unionConstructors)
                         .collect(Collectors.toList());
    }

    private Stream unionConstructors(MemberModel member) {
        List unionConstructors = new ArrayList<>();

        String memberName = member.getFluentSetterMethodName();
        String methodName = "from" + capitalize(memberName);

        unionConstructors.add(MethodSpec.methodBuilder(methodName)
                                        .addJavadoc("$L", member.getUnionConstructorDocumentation())
                                        .addModifiers(PUBLIC, STATIC)
                                        .returns(className())
                                        .addParameter(typeProvider.typeName(member, new TypeNameOptions().useEnumTypes(false)),
                                                      memberName)
                                        .addCode(CodeBlock.of("return builder()." + memberName + "(" + memberName + ").build();"))
                                        .build());

        if (member.getFluentEnumSetterMethodName() != null) {
            unionConstructors.add(MethodSpec.methodBuilder("from" + capitalize(member.getFluentEnumSetterMethodName()))
                                            .addJavadoc("$L", member.getUnionConstructorDocumentation())
                                            .addModifiers(PUBLIC, STATIC)
                                            .returns(className())
                                            .addParameter(typeProvider.typeName(member, new TypeNameOptions().useEnumTypes(true)),
                                                          memberName)
                                            .addCode(CodeBlock.of("return builder()." + member.getFluentEnumSetterMethodName() +
                                                                  "(" + memberName + ").build();"))
                                            .build());
        }

        // Include a consumer-builder if the inner types are structures
        if (!member.isSimple() && !member.isList() && !member.isMap()) {
            TypeName memberType = typeProvider.typeName(member);
            ClassName memberClass = Validate.isInstanceOf(ClassName.class, memberType,
                                                          "Non-simple TypeName was not represented as a ClassName: %s",
                                                          memberType);
            ClassName memberClassBuilder = memberClass.nestedClass("Builder");
            unionConstructors.add(MethodSpec.methodBuilder(methodName)
                                            .addJavadoc("$L", member.getUnionConstructorDocumentation())
                                            .addModifiers(PUBLIC, STATIC)
                                            .returns(className())
                                            .addParameter(ParameterizedTypeName.get(ClassName.get(Consumer.class),
                                                                                    memberClassBuilder),
                                                          memberName)
                                            .addCode(CodeBlock.builder()
                                                              .add("$T builder = $T.builder();", memberClassBuilder, memberClass)
                                                              .add("$L.accept(builder);", memberName)
                                                              .add("return $L(builder.build());", methodName)
                                                              .build())
                                            .build());
        }

        return unionConstructors.stream();
    }

    private MethodSpec unionTypeMethod() {
        return MethodSpec.methodBuilder("type")
                         .addJavadoc("$L", shapeModel.getUnionTypeGetterDocumentation())
                         .addModifiers(PUBLIC)
                         .returns(unionTypeClassName())
                         .addCode("return type;")
                         .build();
    }

    private Collection unionAcceptMethods() {
        return emptyList();
    }

    private void addCasesForMember(MethodSpec.Builder methodBuilder, MemberModel member) {
        methodBuilder.addCode("case $S:", member.getC2jName())
                     .addStatement("return $T.ofNullable(clazz.cast($L()))",
                                   Optional.class,
                                   member.getFluentGetterMethodName());

        if (shouldGenerateDeprecatedNameGetter(member)) {
            methodBuilder.addCode("case $S:", member.getDeprecatedName())
                         .addStatement("return $T.ofNullable(clazz.cast($L()))",
                                       Optional.class,
                                       member.getFluentGetterMethodName());
        }
    }

    private List memberGetters() {
        return shapeModel.getNonStreamingMembers().stream()
                         .filter(m -> !m.getHttp().getIsStreaming())
                         .flatMap(this::memberGetters)
                         .collect(Collectors.toList());
    }

    private Stream memberGetters(MemberModel member) {
        List result = new ArrayList<>();

        if (shouldGenerateEnumGetter(member)) {
            result.add(enumMemberGetter(member));
        }

        member.getAutoConstructClassIfExists()
              .ifPresent(autoConstructClass -> result.add(existenceCheckGetter(member, autoConstructClass)));

        if (shouldGenerateDeprecatedNameGetter(member)) {
            result.add(deprecatedMemberGetter(member));
        }

        result.add(memberGetter(member));

        return checkDeprecated(member, result).stream();
    }

    private boolean shouldGenerateDeprecatedNameGetter(MemberModel member) {
        return StringUtils.isNotBlank(member.getDeprecatedName());
    }

    private boolean shouldGenerateEnumGetter(MemberModel member) {
        return member.getEnumType() != null || MemberCopierSpec.isEnumCopyAvailable(member);
    }

    private MethodSpec enumMemberGetter(MemberModel member) {
        return MethodSpec.methodBuilder(member.getFluentEnumGetterMethodName())
                         .addJavadoc("$L", member.getGetterDocumentation())
                         .addModifiers(PUBLIC)
                         .returns(typeProvider.enumReturnType(member))
                         .addCode(enumGetterStatement(member))
                         .build();
    }

    private MethodSpec memberGetter(MemberModel member) {
        return MethodSpec.methodBuilder(member.getFluentGetterMethodName())
                         .addJavadoc("$L", member.getGetterDocumentation())
                         .addModifiers(PUBLIC)
                         .returns(typeProvider.returnType(member))
                         .addCode(getterStatement(member))
                         .build();
    }

    private MethodSpec existenceCheckGetter(MemberModel member, ClassName autoConstructClass) {
        return MethodSpec.methodBuilder(member.getExistenceCheckMethodName())
                         .addJavadoc("$L", member.getExistenceCheckDocumentation())
                         .addModifiers(PUBLIC)
                         .returns(TypeName.BOOLEAN)
                         .addCode(existenceCheckStatement(member, autoConstructClass))
                         .build();
    }

    private CodeBlock existenceCheckStatement(MemberModel member, ClassName autoConstructClass) {
        String variableName = member.getVariable().getVariableName();
        return CodeBlock.of("return $N != null && !($N instanceof $T);", variableName, variableName, autoConstructClass);
    }

    private MethodSpec deprecatedMemberGetter(MemberModel member) {
        return MethodSpec.methodBuilder(member.getDeprecatedFluentGetterMethodName())
                         .addJavadoc("$L", member.getDeprecatedGetterDocumentation())
                         .addModifiers(PUBLIC)
                         .addAnnotation(Deprecated.class)
                         .returns(typeProvider.returnType(member))
                         .addCode(getterStatement(member))
                         .build();
    }

    private CodeBlock enumGetterStatement(MemberModel member) {
        String fieldName = member.getVariable().getVariableName();
        if (member.isList() || member.isMap()) {
            Optional copier = serviceModelCopiers.copierClassFor(member);
            if (!copier.isPresent()) {
                throw new IllegalStateException("Don't know how to copy " + fieldName + " with enum elements!");
            }
            return CodeBlock.of("return $T.$N($N);", copier.get(), serviceModelCopiers.stringToEnumCopyMethodName(), fieldName);
        } else {
            ClassName enumClass = poetExtensions.getModelClass(member.getEnumType());
            return CodeBlock.of("return $T.fromValue($N);", enumClass, fieldName);
        }
    }

    private CodeBlock getterStatement(MemberModel model) {
        VariableModel modelVariable = model.getVariable();
        return CodeBlock.of("return $N;", modelVariable.getVariableName());
    }

    private List retryableOverrides() {
        if (shapeModel.isRetryable()) {
            MethodSpec isRetryable = MethodSpec.methodBuilder("isRetryableException")
                                               .addAnnotation(Override.class)
                                               .addModifiers(PUBLIC)
                                               .returns(TypeName.BOOLEAN)
                                               .addStatement("return true")
                                               .build();
            if (shapeModel.isThrottling()) {
                MethodSpec isThrottling = MethodSpec.methodBuilder("isThrottlingException")
                                                   .addAnnotation(Override.class)
                                                   .addModifiers(PUBLIC)
                                                   .returns(TypeName.BOOLEAN)
                                                   .addStatement("return true")
                                                   .build();
                return Arrays.asList(isRetryable, isThrottling);
            }
            return Arrays.asList(isRetryable);
        }
        return emptyList();
    }

    private List nestedModelClassTypes() {
        List nestedClasses = new ArrayList<>();
        switch (shapeModel.getShapeType()) {
            case Model:
            case Request:
            case Response:
            case Exception:
                nestedClasses.add(modelBuilderSpecs.builderInterface());
                nestedClasses.add(modelBuilderSpecs.beanStyleBuilder());
                break;
            default:
                break;
        }

        if (shapeModel.isUnion()) {
            nestedClasses.add(modelBuilderSpecs.unionTypeClass());
        }

        return nestedClasses;
    }

    private MethodSpec constructor() {
        MethodSpec.Builder ctorBuilder = MethodSpec.constructorBuilder()
                                                   .addParameter(modelBuilderSpecs.builderImplName(), "builder");

        if (shapeModel.isEvent()) {
            ctorBuilder.addModifiers(Modifier.PROTECTED);
        } else {
            ctorBuilder.addModifiers(PRIVATE);
        }

        if (isRequest() || isResponse()) {
            ctorBuilder.addStatement("super(builder)");
        }

        shapeModelSpec.fields().forEach(f -> ctorBuilder.addStatement("this.$N = builder.$N", f, f));

        if (shapeModel.isUnion()) {
            ctorBuilder.addStatement("this.type = builder.type");
        }

        return ctorBuilder.build();
    }

    private MethodSpec exceptionConstructor() {
        MethodSpec.Builder ctorBuilder = MethodSpec.constructorBuilder()
                                                   .addModifiers(Modifier.PRIVATE)
                                                   .addParameter(modelBuilderSpecs.builderImplName(), "builder");

        ctorBuilder.addStatement("super(builder)");

        shapeModelSpec.fields().forEach(f -> ctorBuilder.addStatement("this.$N = builder.$N", f, f));

        return ctorBuilder.build();
    }

    private MethodSpec builderMethod() {
        return MethodSpec.methodBuilder("builder")
                         .addModifiers(PUBLIC, STATIC)
                         .returns(modelBuilderSpecs.builderInterfaceName())
                         .addStatement("return new $T()", modelBuilderSpecs.builderImplName())
                         .build();
    }

    private MethodSpec toBuilderMethod() {
        return MethodSpec.methodBuilder("toBuilder")
                         .addModifiers(PUBLIC)
                         .addAnnotation(Override.class)
                         .returns(modelBuilderSpecs.builderInterfaceName())
                         .addStatement("return new $T(this)", modelBuilderSpecs.builderImplName())
                         .build();
    }

    private MethodSpec serializableBuilderClass() {
        return MethodSpec.methodBuilder("serializableBuilderClass")
                         .addModifiers(PUBLIC, STATIC)
                         .returns(ParameterizedTypeName.get(ClassName.get(Class.class),
                                                            WildcardTypeName.subtypeOf(modelBuilderSpecs.builderInterfaceName())))
                         .addStatement("return $T.class", modelBuilderSpecs.builderImplName())
                         .build();
    }

    private MethodSpec copyMethod() {
        ParameterizedTypeName consumerParam = ParameterizedTypeName.get(ClassName.get(Consumer.class),
                WildcardTypeName.supertypeOf(modelBuilderSpecs.builderInterfaceName()));

        return MethodSpec.methodBuilder("copy")
                .addModifiers(PUBLIC, FINAL)
                .addAnnotation(Override.class)
                .addParameter(consumerParam, "modifier")
                .addStatement("return $T.super.copy(modifier)", ToCopyableBuilder.class)
                .returns(className())
                .build();
    }

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

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

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

    private List eventStreamInterfaceEventBuilderMethods() {
        return shapeModel.getMembers().stream()
                .filter(m -> m.getShape().isEvent())
                .map(this::eventBuilderMethod)
                .collect(Collectors.toList());
    }

    private MethodSpec eventBuilderMethod(MemberModel event) {
        EventStreamSpecHelper specHelper = new EventStreamSpecHelper(shapeModel, intermediateModel);
        ClassName eventClassName = specHelper.eventClassName(event);

        ClassName returnType;

        if (specHelper.useLegacyGenerationScheme(event)) {
            returnType = eventClassName.nestedClass("Builder");
        } else {
            ClassName baseClass = poetExtensions.getModelClass(event.getShape().getShapeName());
            returnType = baseClass.nestedClass("Builder");
        }

        String methodName = specHelper.eventBuilderMethodName(event);
        return MethodSpec.methodBuilder(methodName)
                .addModifiers(PUBLIC, STATIC)
                .returns(returnType)
                .addJavadoc("Create a builder for the {@code $L} event type for this stream.", event.getC2jName())
                .addStatement("return $T.builder()", eventClassName)
                .build();
    }

    private List addModifier(List specs, Modifier modifier) {
        return specs.stream()
                .map(spec -> addModifier(spec, modifier))
                .collect(Collectors.toList());
    }

    private MethodSpec addModifier(MethodSpec spec, Modifier modifier) {
        return spec.toBuilder().addModifiers(modifier).build();
    }

    private boolean useLegacyEventGenerationScheme(ShapeModel eventStream) {
        EventStreamSpecHelper helper = new EventStreamSpecHelper(eventStream, intermediateModel);
        // This is hacky, but there's no better solution without
        // extensive refactoring. We basically need to know if any of
        // the *event types* within this even stream have this
        // customization enabled, which requires knowing the
        // MemberModel that has this given shape.
        for (MemberModel member : eventStream.getMembers()) {
            if (member.getShape().equals(shapeModel) && helper.useLegacyGenerationScheme(member)) {
                return true;
            }
        }
        return false;
    }

    private Optional findLegacyGenerationEventWithShape(ShapeModel eventStream) {
        EventStreamSpecHelper helper = new EventStreamSpecHelper(eventStream, intermediateModel);
        for (MemberModel member : eventStream.getMembers()) {
            if (member.getShape().equals(shapeModel) && helper.useLegacyGenerationScheme(member)) {
                return Optional.ofNullable(member);
            }
        }
        return Optional.empty();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy