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

software.amazon.awssdk.codegen.poet.waiters.BaseWaiterClassSpec 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.waiters;

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.utils.internal.CodegenNamingUtils.lowercaseFirstChar;

import com.fasterxml.jackson.jr.stree.JrsString;
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.time.Duration;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.lang.model.element.Modifier;
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.annotations.ThreadSafe;
import software.amazon.awssdk.awscore.AwsRequestOverrideConfiguration;
import software.amazon.awssdk.awscore.exception.AwsServiceException;
import software.amazon.awssdk.codegen.emitters.tasks.WaitersRuntimeGeneratorTask;
import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel;
import software.amazon.awssdk.codegen.model.intermediate.OperationModel;
import software.amazon.awssdk.codegen.model.service.Acceptor;
import software.amazon.awssdk.codegen.model.service.WaiterDefinition;
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.core.ApiName;
import software.amazon.awssdk.core.internal.waiters.WaiterAttribute;
import software.amazon.awssdk.core.retry.backoff.BackoffStrategy;
import software.amazon.awssdk.core.retry.backoff.FixedDelayBackoffStrategy;
import software.amazon.awssdk.core.waiters.WaiterAcceptor;
import software.amazon.awssdk.core.waiters.WaiterOverrideConfiguration;
import software.amazon.awssdk.core.waiters.WaiterState;
import software.amazon.awssdk.utils.AttributeMap;
import software.amazon.awssdk.utils.SdkAutoCloseable;

/**
 * Base class containing common logic shared between the sync waiter class and the async waiter class
 */
public abstract class BaseWaiterClassSpec implements ClassSpec {

    private static final String WAITERS_USER_AGENT = "waiter";
    private final IntermediateModel model;
    private final String modelPackage;
    private final Map waiters;
    private final ClassName waiterClassName;
    private final JmesPathAcceptorGenerator jmesPathAcceptorGenerator;
    private final PoetExtension poetExtensions;

    public BaseWaiterClassSpec(IntermediateModel model, ClassName waiterClassName) {
        this.model = model;
        this.modelPackage = model.getMetadata().getFullModelPackageName();
        this.waiters = model.getWaiters();
        this.waiterClassName = waiterClassName;
        this.jmesPathAcceptorGenerator = new JmesPathAcceptorGenerator(waitersRuntimeClass());
        this.poetExtensions = new PoetExtension(model);
    }

    @Override
    public TypeSpec poetSpec() {
        TypeSpec.Builder typeSpecBuilder = PoetUtils.createClassBuilder(className());
        typeSpecBuilder.addAnnotation(SdkInternalApi.class);
        typeSpecBuilder.addAnnotation(ThreadSafe.class);
        typeSpecBuilder.addModifiers(FINAL);
        typeSpecBuilder.addSuperinterface(interfaceClassName());
        typeSpecBuilder.addMethod(constructor());
        typeSpecBuilder.addField(FieldSpec.builder(ParameterizedTypeName.get(WaiterAttribute.class, SdkAutoCloseable.class),
                                                   "CLIENT_ATTRIBUTE", PRIVATE, STATIC, FINAL)
                                          .initializer("new $T<>($T.class)", WaiterAttribute.class, SdkAutoCloseable.class)
                                          .build());
        typeSpecBuilder.addField(clientClassName(), "client", PRIVATE, FINAL);
        typeSpecBuilder.addField(ClassName.get(AttributeMap.class), "managedResources", PRIVATE, FINAL);
        typeSpecBuilder.addMethod(staticErrorCodeMethod());
        typeSpecBuilder.addMethods(waiterOperations());
        typeSpecBuilder.addMethods(waiterAcceptorInitializers());
        typeSpecBuilder.addMethods(waiterConfigInitializers());
        typeSpecBuilder.addFields(waitersFields());
        additionalTypeSpecModification(typeSpecBuilder);

        typeSpecBuilder.addMethod(closeMethod());

        typeSpecBuilder.addMethod(MethodSpec.methodBuilder("builder")
                                            .addModifiers(Modifier.PUBLIC, STATIC)
                                            .returns(interfaceClassName().nestedClass("Builder"))
                                            .addStatement("return new DefaultBuilder()")
                                            .build());

        typeSpecBuilder.addType(builder());
        typeSpecBuilder.addMethod(applyWaitersUserAgentMethod(poetExtensions, model));
        return typeSpecBuilder.build();
    }

    private MethodSpec closeMethod() {
        return MethodSpec.methodBuilder("close")
                         .addAnnotation(Override.class)
                         .addModifiers(PUBLIC)
                         .addStatement("managedResources.close()")
                         .build();
    }

    protected abstract ClassName clientClassName();

    protected abstract TypeName getWaiterResponseType(OperationModel opModel);

    protected abstract ClassName interfaceClassName();

    protected void additionalTypeSpecModification(TypeSpec.Builder type) {
        // no-op
    }

    protected void additionalConstructorInitialization(MethodSpec.Builder method) {
        // no-op
    }

    protected void additionalBuilderTypeSpecModification(TypeSpec.Builder builder) {
        // no-op
    }

    protected Optional additionalWaiterConfig() {
        return Optional.empty();
    }

    private MethodSpec constructor() {
        MethodSpec.Builder ctor = MethodSpec.constructorBuilder()
                                            .addModifiers(PRIVATE)
                                            .addParameter(className().nestedClass("DefaultBuilder"), "builder");
        ctor.addStatement("$T attributeMapBuilder = $T.builder()", ClassName.get(AttributeMap.class).nestedClass("Builder"),
                          AttributeMap.class);
        ctor.beginControlFlow("if (builder.client == null)")
            .addStatement("this.client = $T.builder().build()", clientClassName())
            .addStatement("attributeMapBuilder.put(CLIENT_ATTRIBUTE, this.client)")
            .endControlFlow();
        ctor.beginControlFlow("else")
            .addStatement("this.client = builder.client")
            .endControlFlow();

        additionalConstructorInitialization(ctor);

        ctor.addStatement("managedResources = attributeMapBuilder.build()");

        waiters.entrySet().stream()
               .map(this::waiterFieldInitialization)
               .forEach(ctor::addCode);

        return ctor.build();
    }

    private List waiterConfigInitializers() {
        List initializers = new ArrayList<>();
        waiters.forEach((k, v) -> initializers.add(waiterConfigInitializer(k, v)));
        return initializers;
    }

    private MethodSpec waiterConfigInitializer(String waiterKey, WaiterDefinition waiterDefinition) {
        ClassName overrideConfig = ClassName.get(WaiterOverrideConfiguration.class);
        MethodSpec.Builder configMethod =
            MethodSpec.methodBuilder(waiterFieldName(waiterKey) + "Config")
                      .addModifiers(PRIVATE, STATIC)
                      .addParameter(overrideConfig, "overrideConfig")
                      .returns(overrideConfig);

        configMethod.addStatement("$T<$T> optionalOverrideConfig = Optional.ofNullable(overrideConfig)",
                                  Optional.class,
                                  WaiterOverrideConfiguration.class);
        configMethod.addStatement("int maxAttempts = optionalOverrideConfig.flatMap(WaiterOverrideConfiguration::maxAttempts)"
                                  + ".orElse($L)",
                                  waiterDefinition.getMaxAttempts());
        configMethod.addStatement("$T backoffStrategy = optionalOverrideConfig."
                                  + "flatMap(WaiterOverrideConfiguration::backoffStrategy).orElse($T.create($T.ofSeconds($L)))",
                                  BackoffStrategy.class,
                                  FixedDelayBackoffStrategy.class,
                                  Duration.class,
                                  waiterDefinition.getDelay());
        configMethod.addStatement("$T waitTimeout = optionalOverrideConfig.flatMap(WaiterOverrideConfiguration::waitTimeout)"
                                  + ".orElse(null)",
                                  Duration.class);

        configMethod.addStatement("return WaiterOverrideConfiguration.builder().maxAttempts(maxAttempts).backoffStrategy"
                                  + "(backoffStrategy).waitTimeout(waitTimeout).build()");
        return configMethod.build();
    }

    private CodeBlock waiterFieldInitialization(Map.Entry waiterDefinition) {
        String waiterKey = waiterDefinition.getKey();
        WaiterDefinition waiter = waiterDefinition.getValue();
        OperationModel opModel = operationModel(waiter);
        CodeBlock.Builder codeBlockBuilder = CodeBlock
            .builder();

        String waiterFieldName = waiterFieldName(waiterKey);
        codeBlockBuilder.add("this.$L = $T.builder($T.class)"
                             + ".acceptors($LAcceptors()).overrideConfiguration($LConfig(builder.overrideConfiguration))",
                             waiterFieldName,
                             waiterClassName,
                             ClassName.get(modelPackage, opModel.getReturnType().getReturnType()),
                             waiterFieldName,
                             waiterFieldName);

        additionalWaiterConfig().ifPresent(codeBlockBuilder::add);
        codeBlockBuilder.addStatement(".build()");
        return codeBlockBuilder.build();
    }

    private List waitersFields() {
        return waiters.entrySet().stream()
                      .map(this::waiterField)
                      .collect(Collectors.toList());
    }

    private FieldSpec waiterField(Map.Entry waiterDefinition) {
        OperationModel opModel = operationModel(waiterDefinition.getValue());
        ClassName pojoResponse = ClassName.get(modelPackage, opModel.getReturnType().getReturnType());
        String fieldName = waiterFieldName(waiterDefinition.getKey());
        return FieldSpec.builder(ParameterizedTypeName.get(waiterClassName,
                                                           pojoResponse), fieldName)
                        .addModifiers(PRIVATE, FINAL)
                        .build();
    }

    private TypeSpec builder() {
        TypeSpec.Builder builder = TypeSpec.classBuilder("DefaultBuilder")
                                           .addModifiers(PUBLIC, STATIC, FINAL)
                                           .addSuperinterface(interfaceClassName().nestedClass("Builder"))
                                           .addField(clientClassName(), "client", PRIVATE)
                                           .addField(ClassName.get(WaiterOverrideConfiguration.class),
                                                     "overrideConfiguration", PRIVATE);

        additionalBuilderTypeSpecModification(builder);
        builder.addMethods(builderMethods());
        builder.addMethod(MethodSpec.constructorBuilder()
                                    .addModifiers(PRIVATE)
                                    .build());
        return builder.build();
    }

    private List builderMethods() {
        List methods = new ArrayList<>();
        methods.add(MethodSpec.methodBuilder("overrideConfiguration")
                              .addModifiers(Modifier.PUBLIC)
                              .addAnnotation(Override.class)
                              .addParameter(ClassName.get(WaiterOverrideConfiguration.class), "overrideConfiguration")
                              .addStatement("this.overrideConfiguration = overrideConfiguration")
                              .addStatement("return this")
                              .returns(interfaceClassName().nestedClass("Builder"))
                              .build());
        methods.add(MethodSpec.methodBuilder("client")
                              .addModifiers(Modifier.PUBLIC)
                              .addAnnotation(Override.class)
                              .addParameter(clientClassName(), "client")
                              .addStatement("this.client = client")
                              .addStatement("return this")
                              .returns(interfaceClassName().nestedClass("Builder"))
                              .build());
        methods.add(MethodSpec.methodBuilder("build")
                              .addModifiers(Modifier.PUBLIC)
                              .returns(interfaceClassName())
                              .addStatement("return new $T(this)", className())
                              .build());
        return methods;

    }

    private List waiterOperations() {
        return waiters.entrySet()
                      .stream()
                      .flatMap(this::waiterOperations)
                      .sorted(Comparator.comparing(m -> m.name))
                      .collect(Collectors.toList());
    }

    private Stream waiterOperations(Map.Entry waiterDefinition) {
        List methods = new ArrayList<>();
        methods.add(waiterOperation(waiterDefinition));
        methods.add(waiterOperationWithOverrideConfig(waiterDefinition));
        return methods.stream();
    }

    private MethodSpec waiterOperationWithOverrideConfig(Map.Entry waiterDefinition) {
        String waiterMethodName = waiterDefinition.getKey();
        OperationModel opModel = operationModel(waiterDefinition.getValue());

        ClassName overrideConfig = ClassName.get(WaiterOverrideConfiguration.class);
        ClassName requestType = ClassName.get(modelPackage, opModel.getInput().getVariableType());

        String waiterFieldName = waiterFieldName(waiterDefinition.getKey());
        MethodSpec.Builder builder = methodSignatureWithReturnType(waiterMethodName, opModel)
            .addParameter(requestType, opModel.getInput().getVariableName())
            .addParameter(overrideConfig, "overrideConfig")
            .addModifiers(PUBLIC)
            .addAnnotation(Override.class)
            .addStatement("return $L.$L(() -> client.$N(applyWaitersUserAgent($N)), $LConfig(overrideConfig))",
                          waiterFieldName,
                          waiterClassName.simpleName().equals("Waiter") ? "run" : "runAsync",
                          lowercaseFirstChar(waiterDefinition.getValue().getOperation()),
                          opModel.getInput().getVariableName(),
                          waiterFieldName);

        return builder.build();
    }

    private MethodSpec waiterOperation(Map.Entry waiterDefinition) {
        String waiterMethodName = waiterDefinition.getKey();
        OperationModel opModel = operationModel(waiterDefinition.getValue());

        ClassName requestType = ClassName.get(modelPackage, opModel.getInput().getVariableType());

        MethodSpec.Builder builder = methodSignatureWithReturnType(waiterMethodName, opModel)
            .addParameter(requestType, opModel.getInput().getVariableName())
            .addModifiers(PUBLIC)
            .addAnnotation(Override.class)
            .addStatement("return $L.$L(() -> client.$N(applyWaitersUserAgent($N)))",
                          waiterFieldName(waiterMethodName),
                          waiterClassName.simpleName().equals("Waiter") ? "run" : "runAsync",
                          lowercaseFirstChar(waiterDefinition.getValue().getOperation()),
                          opModel.getInput().getVariableName());

        return builder.build();
    }

    private List waiterAcceptorInitializers() {
        List initializers = new ArrayList<>();
        waiters.forEach((k, v) -> initializers.add(acceptorInitializer(k, v)));
        return initializers;
    }

    private MethodSpec acceptorInitializer(String waiterKey, WaiterDefinition waiterDefinition) {
        MethodSpec.Builder acceptorsMethod =
            MethodSpec.methodBuilder(waiterFieldName(waiterKey) + "Acceptors")
                      .addModifiers(PRIVATE, STATIC)
                      .returns(waiterAcceptorTypeName(waiterDefinition));

        acceptorsMethod.addStatement("$T result = new $T<>()", waiterAcceptorTypeName(waiterDefinition), ArrayList.class);

        for (Acceptor acceptor : waiterDefinition.getAcceptors()) {
            acceptorsMethod.addCode("result.add(")
                           .addCode(acceptor(acceptor))
                           .addCode(");");
        }

        acceptorsMethod.addStatement("result.addAll($T.DEFAULT_ACCEPTORS)", waitersRuntimeClass());

        acceptorsMethod.addStatement("return result");

        return acceptorsMethod.build();
    }

    protected String waiterFieldName(String waiterKey) {
        return lowercaseFirstChar(waiterKey) + "Waiter";
    }

    private OperationModel operationModel(WaiterDefinition waiterDefinition) {
        return model.getOperation(waiterDefinition.getOperation());
    }

    private MethodSpec.Builder methodSignatureWithReturnType(String waiterMethodName, OperationModel opModel) {
        return MethodSpec.methodBuilder(getWaiterMethodName(waiterMethodName))
                         .returns(getWaiterResponseType(opModel));
    }

    static MethodSpec applyWaitersUserAgentMethod(PoetExtension poetExtensions, IntermediateModel model) {

        TypeVariableName typeVariableName =
            TypeVariableName.get("T", poetExtensions.getModelClass(model.getSdkRequestBaseClassName()));

        ParameterizedTypeName parameterizedTypeName = ParameterizedTypeName
            .get(ClassName.get(Consumer.class), ClassName.get(AwsRequestOverrideConfiguration.Builder.class));

        CodeBlock codeBlock = CodeBlock.builder()
                                       .addStatement("$T userAgentApplier = b -> b.addApiName($T.builder().version"
                                                     + "($S).name($S).build())",
                                                     parameterizedTypeName, ApiName.class,
                                                     WAITERS_USER_AGENT,
                                                     "hll")
                                       .addStatement("$T overrideConfiguration =\n"
                                                     + "            request.overrideConfiguration().map(c -> c.toBuilder()"
                                                     + ".applyMutation"
                                                     + "(userAgentApplier).build())\n"
                                                     + "            .orElse((AwsRequestOverrideConfiguration.builder()"
                                                     + ".applyMutation"
                                                     + "(userAgentApplier).build()))", AwsRequestOverrideConfiguration.class)
                                       .addStatement("return (T) request.toBuilder().overrideConfiguration"
                                                     + "(overrideConfiguration).build()")
                                       .build();

        return MethodSpec.methodBuilder("applyWaitersUserAgent")
                         .addModifiers(Modifier.PRIVATE)
                         .addParameter(typeVariableName, "request")
                         .addTypeVariable(typeVariableName)
                         .addCode(codeBlock)
                         .returns(typeVariableName)
                         .build();
    }

    private String getWaiterMethodName(String waiterMethodName) {
        return "waitUntil" + waiterMethodName;
    }

    private TypeName waiterAcceptorTypeName(WaiterDefinition waiterDefinition) {
        WildcardTypeName wildcardTypeName = WildcardTypeName.supertypeOf(fullyQualifiedResponseType(waiterDefinition));

        return ParameterizedTypeName.get(ClassName.get(List.class),
                                         ParameterizedTypeName.get(ClassName.get(WaiterAcceptor.class), wildcardTypeName));
    }

    private TypeName fullyQualifiedResponseType(WaiterDefinition waiterDefinition) {
        String modelPackage = model.getMetadata().getFullModelPackageName();
        String operationResponseType = model.getOperation(waiterDefinition.getOperation()).getReturnType().getReturnType();
        return ClassName.get(modelPackage, operationResponseType);
    }

    private CodeBlock acceptor(Acceptor acceptor) {
        CodeBlock.Builder result = CodeBlock.builder();

        switch (acceptor.getState()) {
            case "success":
                result.add("$T.success", WaiterAcceptor.class);
                break;
            case "failure":
                result.add("$T.error", WaiterAcceptor.class);
                break;
            case "retry":
                result.add("$T.retry", WaiterAcceptor.class);
                break;
            default:
                throw new IllegalArgumentException("Unsupported acceptor state: " + acceptor.getState());
        }

        switch (acceptor.getMatcher()) {
            case "path":
                result.add("OnResponseAcceptor(");
                result.add(pathAcceptorBody(acceptor));
                result.add(")");
                break;
            case "pathAll":
                result.add("OnResponseAcceptor(");
                result.add(pathAllAcceptorBody(acceptor));
                result.add(")");
                break;
            case "pathAny":
                result.add("OnResponseAcceptor(");
                result.add(pathAnyAcceptorBody(acceptor));
                result.add(")");
                break;
            case "status":
                // Note: Ignores the result we've built so far because this uses a special acceptor implementation.
                int expected = Integer.parseInt(acceptor.getExpected().asText());
                return CodeBlock.of("new $T($L, $T.$L)", waitersRuntimeClass().nestedClass("ResponseStatusAcceptor"),
                                    expected, WaiterState.class, waiterState(acceptor));
            case "error":
                result.add("OnExceptionAcceptor(");
                result.add(errorAcceptorBody(acceptor));
                result.add(")");
                break;
            default:
                throw new IllegalArgumentException("Unsupported acceptor matcher: " + acceptor.getMatcher());
        }

        return result.build();
    }

    private String waiterState(Acceptor acceptor) {
        switch (acceptor.getState()) {
            case "success":
                return WaiterState.SUCCESS.name();
            case "failure":
                return WaiterState.FAILURE.name();
            case "retry":
                return WaiterState.RETRY.name();
            default:
                throw new IllegalArgumentException("Unsupported acceptor state: " + acceptor.getState());
        }
    }

    private CodeBlock pathAcceptorBody(Acceptor acceptor) {
        String expected = acceptor.getExpected().asText();
        String expectedType = acceptor.getExpected() instanceof JrsString ? "$S" : "$L";
        return CodeBlock.builder()
                        .add("response -> {")
                        .add("$1T input = new $1T(response);", waitersRuntimeClass().nestedClass("Value"))
                        .add("return $T.equals(", Objects.class)
                        .add(jmesPathAcceptorGenerator.interpret(acceptor.getArgument(), "input"))
                        .add(".value(), " + expectedType + ");", expected)
                        .add("}")
                        .build();
    }

    private CodeBlock pathAllAcceptorBody(Acceptor acceptor) {
        String expected = acceptor.getExpected().asText();
        String expectedType = acceptor.getExpected() instanceof JrsString ? "$S" : "$L";
        return CodeBlock.builder()
                        .add("response -> {")
                        .add("$1T input = new $1T(response);", waitersRuntimeClass().nestedClass("Value"))
                        .add("$T<$T> resultValues = ", List.class, Object.class)
                        .add(jmesPathAcceptorGenerator.interpret(acceptor.getArgument(), "input"))
                        .add(".values();")
                        .add("return !resultValues.isEmpty() && "
                             + "resultValues.stream().allMatch(v -> $T.equals(v, " + expectedType + "));",
                             Objects.class, expected)
                        .add("}")
                        .build();
    }

    private CodeBlock pathAnyAcceptorBody(Acceptor acceptor) {
        String expected = acceptor.getExpected().asText();
        String expectedType = acceptor.getExpected() instanceof JrsString ? "$S" : "$L";
        return CodeBlock.builder()
                        .add("response -> {")
                        .add("$1T input = new $1T(response);", waitersRuntimeClass().nestedClass("Value"))
                        .add("$T<$T> resultValues = ", List.class, Object.class)
                        .add(jmesPathAcceptorGenerator.interpret(acceptor.getArgument(), "input"))
                        .add(".values();")
                        .add("return !resultValues.isEmpty() && "
                             + "resultValues.stream().anyMatch(v -> $T.equals(v, " + expectedType + "));",
                             Objects.class, expected)
                        .add("}")
                        .build();
    }

    private CodeBlock errorAcceptorBody(Acceptor acceptor) {
        String expected = acceptor.getExpected().asText();
        String expectedType = acceptor.getExpected() instanceof JrsString ? "$S" : "$L";
        return CodeBlock.of("error -> $T.equals(errorCode(error), " + expectedType + ")", Objects.class, expected);
    }

    private MethodSpec staticErrorCodeMethod() {
        return MethodSpec.methodBuilder("errorCode")
                         .addModifiers(PRIVATE, STATIC)
                         .returns(String.class)
                         .addParameter(Throwable.class, "error")
                         .addCode("if (error instanceof $T) {", AwsServiceException.class)
                         .addCode("return (($T) error).awsErrorDetails().errorCode();", AwsServiceException.class)
                         .addCode("}")
                         .addCode("return null;")
                         .build();
    }

    private ClassName waitersRuntimeClass() {
        return ClassName.get(model.getMetadata().getFullWaitersInternalPackageName(),
                             WaitersRuntimeGeneratorTask.RUNTIME_CLASS_NAME);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy