• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License").
5  * You may not use this file except in compliance with the License.
6  * A copy of the License is located at
7  *
8  *  http://aws.amazon.com/apache2.0
9  *
10  * or in the "license" file accompanying this file. This file is distributed
11  * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12  * express or implied. See the License for the specific language governing
13  * permissions and limitations under the License.
14  */
15 
16 package software.amazon.awssdk.codegen.poet.waiters;
17 
18 import static javax.lang.model.element.Modifier.FINAL;
19 import static javax.lang.model.element.Modifier.PRIVATE;
20 import static javax.lang.model.element.Modifier.PUBLIC;
21 import static javax.lang.model.element.Modifier.STATIC;
22 import static software.amazon.awssdk.utils.internal.CodegenNamingUtils.lowercaseFirstChar;
23 
24 import com.fasterxml.jackson.jr.stree.JrsString;
25 import com.squareup.javapoet.ClassName;
26 import com.squareup.javapoet.CodeBlock;
27 import com.squareup.javapoet.FieldSpec;
28 import com.squareup.javapoet.MethodSpec;
29 import com.squareup.javapoet.ParameterizedTypeName;
30 import com.squareup.javapoet.TypeName;
31 import com.squareup.javapoet.TypeSpec;
32 import com.squareup.javapoet.TypeVariableName;
33 import com.squareup.javapoet.WildcardTypeName;
34 import java.time.Duration;
35 import java.util.ArrayList;
36 import java.util.Comparator;
37 import java.util.List;
38 import java.util.Map;
39 import java.util.Objects;
40 import java.util.Optional;
41 import java.util.function.Consumer;
42 import java.util.stream.Collectors;
43 import java.util.stream.Stream;
44 import javax.lang.model.element.Modifier;
45 import software.amazon.awssdk.annotations.SdkInternalApi;
46 import software.amazon.awssdk.annotations.ThreadSafe;
47 import software.amazon.awssdk.awscore.AwsRequestOverrideConfiguration;
48 import software.amazon.awssdk.awscore.exception.AwsServiceException;
49 import software.amazon.awssdk.codegen.emitters.tasks.WaitersRuntimeGeneratorTask;
50 import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel;
51 import software.amazon.awssdk.codegen.model.intermediate.OperationModel;
52 import software.amazon.awssdk.codegen.model.service.Acceptor;
53 import software.amazon.awssdk.codegen.model.service.WaiterDefinition;
54 import software.amazon.awssdk.codegen.poet.ClassSpec;
55 import software.amazon.awssdk.codegen.poet.PoetExtension;
56 import software.amazon.awssdk.codegen.poet.PoetUtils;
57 import software.amazon.awssdk.core.ApiName;
58 import software.amazon.awssdk.core.internal.waiters.WaiterAttribute;
59 import software.amazon.awssdk.core.retry.backoff.BackoffStrategy;
60 import software.amazon.awssdk.core.retry.backoff.FixedDelayBackoffStrategy;
61 import software.amazon.awssdk.core.waiters.WaiterAcceptor;
62 import software.amazon.awssdk.core.waiters.WaiterOverrideConfiguration;
63 import software.amazon.awssdk.core.waiters.WaiterState;
64 import software.amazon.awssdk.utils.AttributeMap;
65 import software.amazon.awssdk.utils.SdkAutoCloseable;
66 
67 /**
68  * Base class containing common logic shared between the sync waiter class and the async waiter class
69  */
70 public abstract class BaseWaiterClassSpec implements ClassSpec {
71 
72     private static final String WAITERS_USER_AGENT = "waiter";
73     private final IntermediateModel model;
74     private final String modelPackage;
75     private final Map<String, WaiterDefinition> waiters;
76     private final ClassName waiterClassName;
77     private final JmesPathAcceptorGenerator jmesPathAcceptorGenerator;
78     private final PoetExtension poetExtensions;
79 
BaseWaiterClassSpec(IntermediateModel model, ClassName waiterClassName)80     public BaseWaiterClassSpec(IntermediateModel model, ClassName waiterClassName) {
81         this.model = model;
82         this.modelPackage = model.getMetadata().getFullModelPackageName();
83         this.waiters = model.getWaiters();
84         this.waiterClassName = waiterClassName;
85         this.jmesPathAcceptorGenerator = new JmesPathAcceptorGenerator(waitersRuntimeClass());
86         this.poetExtensions = new PoetExtension(model);
87     }
88 
89     @Override
poetSpec()90     public TypeSpec poetSpec() {
91         TypeSpec.Builder typeSpecBuilder = PoetUtils.createClassBuilder(className());
92         typeSpecBuilder.addAnnotation(SdkInternalApi.class);
93         typeSpecBuilder.addAnnotation(ThreadSafe.class);
94         typeSpecBuilder.addModifiers(FINAL);
95         typeSpecBuilder.addSuperinterface(interfaceClassName());
96         typeSpecBuilder.addMethod(constructor());
97         typeSpecBuilder.addField(FieldSpec.builder(ParameterizedTypeName.get(WaiterAttribute.class, SdkAutoCloseable.class),
98                                                    "CLIENT_ATTRIBUTE", PRIVATE, STATIC, FINAL)
99                                           .initializer("new $T<>($T.class)", WaiterAttribute.class, SdkAutoCloseable.class)
100                                           .build());
101         typeSpecBuilder.addField(clientClassName(), "client", PRIVATE, FINAL);
102         typeSpecBuilder.addField(ClassName.get(AttributeMap.class), "managedResources", PRIVATE, FINAL);
103         typeSpecBuilder.addMethod(staticErrorCodeMethod());
104         typeSpecBuilder.addMethods(waiterOperations());
105         typeSpecBuilder.addMethods(waiterAcceptorInitializers());
106         typeSpecBuilder.addMethods(waiterConfigInitializers());
107         typeSpecBuilder.addFields(waitersFields());
108         additionalTypeSpecModification(typeSpecBuilder);
109 
110         typeSpecBuilder.addMethod(closeMethod());
111 
112         typeSpecBuilder.addMethod(MethodSpec.methodBuilder("builder")
113                                             .addModifiers(Modifier.PUBLIC, STATIC)
114                                             .returns(interfaceClassName().nestedClass("Builder"))
115                                             .addStatement("return new DefaultBuilder()")
116                                             .build());
117 
118         typeSpecBuilder.addType(builder());
119         typeSpecBuilder.addMethod(applyWaitersUserAgentMethod(poetExtensions, model));
120         return typeSpecBuilder.build();
121     }
122 
closeMethod()123     private MethodSpec closeMethod() {
124         return MethodSpec.methodBuilder("close")
125                          .addAnnotation(Override.class)
126                          .addModifiers(PUBLIC)
127                          .addStatement("managedResources.close()")
128                          .build();
129     }
130 
clientClassName()131     protected abstract ClassName clientClassName();
132 
getWaiterResponseType(OperationModel opModel)133     protected abstract TypeName getWaiterResponseType(OperationModel opModel);
134 
interfaceClassName()135     protected abstract ClassName interfaceClassName();
136 
additionalTypeSpecModification(TypeSpec.Builder type)137     protected void additionalTypeSpecModification(TypeSpec.Builder type) {
138         // no-op
139     }
140 
additionalConstructorInitialization(MethodSpec.Builder method)141     protected void additionalConstructorInitialization(MethodSpec.Builder method) {
142         // no-op
143     }
144 
additionalBuilderTypeSpecModification(TypeSpec.Builder builder)145     protected void additionalBuilderTypeSpecModification(TypeSpec.Builder builder) {
146         // no-op
147     }
148 
additionalWaiterConfig()149     protected Optional<String> additionalWaiterConfig() {
150         return Optional.empty();
151     }
152 
constructor()153     private MethodSpec constructor() {
154         MethodSpec.Builder ctor = MethodSpec.constructorBuilder()
155                                             .addModifiers(PRIVATE)
156                                             .addParameter(className().nestedClass("DefaultBuilder"), "builder");
157         ctor.addStatement("$T attributeMapBuilder = $T.builder()", ClassName.get(AttributeMap.class).nestedClass("Builder"),
158                           AttributeMap.class);
159         ctor.beginControlFlow("if (builder.client == null)")
160             .addStatement("this.client = $T.builder().build()", clientClassName())
161             .addStatement("attributeMapBuilder.put(CLIENT_ATTRIBUTE, this.client)")
162             .endControlFlow();
163         ctor.beginControlFlow("else")
164             .addStatement("this.client = builder.client")
165             .endControlFlow();
166 
167         additionalConstructorInitialization(ctor);
168 
169         ctor.addStatement("managedResources = attributeMapBuilder.build()");
170 
171         waiters.entrySet().stream()
172                .map(this::waiterFieldInitialization)
173                .forEach(ctor::addCode);
174 
175         return ctor.build();
176     }
177 
waiterConfigInitializers()178     private List<MethodSpec> waiterConfigInitializers() {
179         List<MethodSpec> initializers = new ArrayList<>();
180         waiters.forEach((k, v) -> initializers.add(waiterConfigInitializer(k, v)));
181         return initializers;
182     }
183 
waiterConfigInitializer(String waiterKey, WaiterDefinition waiterDefinition)184     private MethodSpec waiterConfigInitializer(String waiterKey, WaiterDefinition waiterDefinition) {
185         ClassName overrideConfig = ClassName.get(WaiterOverrideConfiguration.class);
186         MethodSpec.Builder configMethod =
187             MethodSpec.methodBuilder(waiterFieldName(waiterKey) + "Config")
188                       .addModifiers(PRIVATE, STATIC)
189                       .addParameter(overrideConfig, "overrideConfig")
190                       .returns(overrideConfig);
191 
192         configMethod.addStatement("$T<$T> optionalOverrideConfig = Optional.ofNullable(overrideConfig)",
193                                   Optional.class,
194                                   WaiterOverrideConfiguration.class);
195         configMethod.addStatement("int maxAttempts = optionalOverrideConfig.flatMap(WaiterOverrideConfiguration::maxAttempts)"
196                                   + ".orElse($L)",
197                                   waiterDefinition.getMaxAttempts());
198         configMethod.addStatement("$T backoffStrategy = optionalOverrideConfig."
199                                   + "flatMap(WaiterOverrideConfiguration::backoffStrategy).orElse($T.create($T.ofSeconds($L)))",
200                                   BackoffStrategy.class,
201                                   FixedDelayBackoffStrategy.class,
202                                   Duration.class,
203                                   waiterDefinition.getDelay());
204         configMethod.addStatement("$T waitTimeout = optionalOverrideConfig.flatMap(WaiterOverrideConfiguration::waitTimeout)"
205                                   + ".orElse(null)",
206                                   Duration.class);
207 
208         configMethod.addStatement("return WaiterOverrideConfiguration.builder().maxAttempts(maxAttempts).backoffStrategy"
209                                   + "(backoffStrategy).waitTimeout(waitTimeout).build()");
210         return configMethod.build();
211     }
212 
waiterFieldInitialization(Map.Entry<String, WaiterDefinition> waiterDefinition)213     private CodeBlock waiterFieldInitialization(Map.Entry<String, WaiterDefinition> waiterDefinition) {
214         String waiterKey = waiterDefinition.getKey();
215         WaiterDefinition waiter = waiterDefinition.getValue();
216         OperationModel opModel = operationModel(waiter);
217         CodeBlock.Builder codeBlockBuilder = CodeBlock
218             .builder();
219 
220         String waiterFieldName = waiterFieldName(waiterKey);
221         codeBlockBuilder.add("this.$L = $T.builder($T.class)"
222                              + ".acceptors($LAcceptors()).overrideConfiguration($LConfig(builder.overrideConfiguration))",
223                              waiterFieldName,
224                              waiterClassName,
225                              ClassName.get(modelPackage, opModel.getReturnType().getReturnType()),
226                              waiterFieldName,
227                              waiterFieldName);
228 
229         additionalWaiterConfig().ifPresent(codeBlockBuilder::add);
230         codeBlockBuilder.addStatement(".build()");
231         return codeBlockBuilder.build();
232     }
233 
waitersFields()234     private List<FieldSpec> waitersFields() {
235         return waiters.entrySet().stream()
236                       .map(this::waiterField)
237                       .collect(Collectors.toList());
238     }
239 
waiterField(Map.Entry<String, WaiterDefinition> waiterDefinition)240     private FieldSpec waiterField(Map.Entry<String, WaiterDefinition> waiterDefinition) {
241         OperationModel opModel = operationModel(waiterDefinition.getValue());
242         ClassName pojoResponse = ClassName.get(modelPackage, opModel.getReturnType().getReturnType());
243         String fieldName = waiterFieldName(waiterDefinition.getKey());
244         return FieldSpec.builder(ParameterizedTypeName.get(waiterClassName,
245                                                            pojoResponse), fieldName)
246                         .addModifiers(PRIVATE, FINAL)
247                         .build();
248     }
249 
builder()250     private TypeSpec builder() {
251         TypeSpec.Builder builder = TypeSpec.classBuilder("DefaultBuilder")
252                                            .addModifiers(PUBLIC, STATIC, FINAL)
253                                            .addSuperinterface(interfaceClassName().nestedClass("Builder"))
254                                            .addField(clientClassName(), "client", PRIVATE)
255                                            .addField(ClassName.get(WaiterOverrideConfiguration.class),
256                                                      "overrideConfiguration", PRIVATE);
257 
258         additionalBuilderTypeSpecModification(builder);
259         builder.addMethods(builderMethods());
260         builder.addMethod(MethodSpec.constructorBuilder()
261                                     .addModifiers(PRIVATE)
262                                     .build());
263         return builder.build();
264     }
265 
builderMethods()266     private List<MethodSpec> builderMethods() {
267         List<MethodSpec> methods = new ArrayList<>();
268         methods.add(MethodSpec.methodBuilder("overrideConfiguration")
269                               .addModifiers(Modifier.PUBLIC)
270                               .addAnnotation(Override.class)
271                               .addParameter(ClassName.get(WaiterOverrideConfiguration.class), "overrideConfiguration")
272                               .addStatement("this.overrideConfiguration = overrideConfiguration")
273                               .addStatement("return this")
274                               .returns(interfaceClassName().nestedClass("Builder"))
275                               .build());
276         methods.add(MethodSpec.methodBuilder("client")
277                               .addModifiers(Modifier.PUBLIC)
278                               .addAnnotation(Override.class)
279                               .addParameter(clientClassName(), "client")
280                               .addStatement("this.client = client")
281                               .addStatement("return this")
282                               .returns(interfaceClassName().nestedClass("Builder"))
283                               .build());
284         methods.add(MethodSpec.methodBuilder("build")
285                               .addModifiers(Modifier.PUBLIC)
286                               .returns(interfaceClassName())
287                               .addStatement("return new $T(this)", className())
288                               .build());
289         return methods;
290 
291     }
292 
waiterOperations()293     private List<MethodSpec> waiterOperations() {
294         return waiters.entrySet()
295                       .stream()
296                       .flatMap(this::waiterOperations)
297                       .sorted(Comparator.comparing(m -> m.name))
298                       .collect(Collectors.toList());
299     }
300 
waiterOperations(Map.Entry<String, WaiterDefinition> waiterDefinition)301     private Stream<MethodSpec> waiterOperations(Map.Entry<String, WaiterDefinition> waiterDefinition) {
302         List<MethodSpec> methods = new ArrayList<>();
303         methods.add(waiterOperation(waiterDefinition));
304         methods.add(waiterOperationWithOverrideConfig(waiterDefinition));
305         return methods.stream();
306     }
307 
waiterOperationWithOverrideConfig(Map.Entry<String, WaiterDefinition> waiterDefinition)308     private MethodSpec waiterOperationWithOverrideConfig(Map.Entry<String, WaiterDefinition> waiterDefinition) {
309         String waiterMethodName = waiterDefinition.getKey();
310         OperationModel opModel = operationModel(waiterDefinition.getValue());
311 
312         ClassName overrideConfig = ClassName.get(WaiterOverrideConfiguration.class);
313         ClassName requestType = ClassName.get(modelPackage, opModel.getInput().getVariableType());
314 
315         String waiterFieldName = waiterFieldName(waiterDefinition.getKey());
316         MethodSpec.Builder builder = methodSignatureWithReturnType(waiterMethodName, opModel)
317             .addParameter(requestType, opModel.getInput().getVariableName())
318             .addParameter(overrideConfig, "overrideConfig")
319             .addModifiers(PUBLIC)
320             .addAnnotation(Override.class)
321             .addStatement("return $L.$L(() -> client.$N(applyWaitersUserAgent($N)), $LConfig(overrideConfig))",
322                           waiterFieldName,
323                           waiterClassName.simpleName().equals("Waiter") ? "run" : "runAsync",
324                           lowercaseFirstChar(waiterDefinition.getValue().getOperation()),
325                           opModel.getInput().getVariableName(),
326                           waiterFieldName);
327 
328         return builder.build();
329     }
330 
waiterOperation(Map.Entry<String, WaiterDefinition> waiterDefinition)331     private MethodSpec waiterOperation(Map.Entry<String, WaiterDefinition> waiterDefinition) {
332         String waiterMethodName = waiterDefinition.getKey();
333         OperationModel opModel = operationModel(waiterDefinition.getValue());
334 
335         ClassName requestType = ClassName.get(modelPackage, opModel.getInput().getVariableType());
336 
337         MethodSpec.Builder builder = methodSignatureWithReturnType(waiterMethodName, opModel)
338             .addParameter(requestType, opModel.getInput().getVariableName())
339             .addModifiers(PUBLIC)
340             .addAnnotation(Override.class)
341             .addStatement("return $L.$L(() -> client.$N(applyWaitersUserAgent($N)))",
342                           waiterFieldName(waiterMethodName),
343                           waiterClassName.simpleName().equals("Waiter") ? "run" : "runAsync",
344                           lowercaseFirstChar(waiterDefinition.getValue().getOperation()),
345                           opModel.getInput().getVariableName());
346 
347         return builder.build();
348     }
349 
waiterAcceptorInitializers()350     private List<MethodSpec> waiterAcceptorInitializers() {
351         List<MethodSpec> initializers = new ArrayList<>();
352         waiters.forEach((k, v) -> initializers.add(acceptorInitializer(k, v)));
353         return initializers;
354     }
355 
acceptorInitializer(String waiterKey, WaiterDefinition waiterDefinition)356     private MethodSpec acceptorInitializer(String waiterKey, WaiterDefinition waiterDefinition) {
357         MethodSpec.Builder acceptorsMethod =
358             MethodSpec.methodBuilder(waiterFieldName(waiterKey) + "Acceptors")
359                       .addModifiers(PRIVATE, STATIC)
360                       .returns(waiterAcceptorTypeName(waiterDefinition));
361 
362         acceptorsMethod.addStatement("$T result = new $T<>()", waiterAcceptorTypeName(waiterDefinition), ArrayList.class);
363 
364         for (Acceptor acceptor : waiterDefinition.getAcceptors()) {
365             acceptorsMethod.addCode("result.add(")
366                            .addCode(acceptor(acceptor))
367                            .addCode(");");
368         }
369 
370         acceptorsMethod.addStatement("result.addAll($T.DEFAULT_ACCEPTORS)", waitersRuntimeClass());
371 
372         acceptorsMethod.addStatement("return result");
373 
374         return acceptorsMethod.build();
375     }
376 
waiterFieldName(String waiterKey)377     protected String waiterFieldName(String waiterKey) {
378         return lowercaseFirstChar(waiterKey) + "Waiter";
379     }
380 
operationModel(WaiterDefinition waiterDefinition)381     private OperationModel operationModel(WaiterDefinition waiterDefinition) {
382         return model.getOperation(waiterDefinition.getOperation());
383     }
384 
methodSignatureWithReturnType(String waiterMethodName, OperationModel opModel)385     private MethodSpec.Builder methodSignatureWithReturnType(String waiterMethodName, OperationModel opModel) {
386         return MethodSpec.methodBuilder(getWaiterMethodName(waiterMethodName))
387                          .returns(getWaiterResponseType(opModel));
388     }
389 
applyWaitersUserAgentMethod(PoetExtension poetExtensions, IntermediateModel model)390     static MethodSpec applyWaitersUserAgentMethod(PoetExtension poetExtensions, IntermediateModel model) {
391 
392         TypeVariableName typeVariableName =
393             TypeVariableName.get("T", poetExtensions.getModelClass(model.getSdkRequestBaseClassName()));
394 
395         ParameterizedTypeName parameterizedTypeName = ParameterizedTypeName
396             .get(ClassName.get(Consumer.class), ClassName.get(AwsRequestOverrideConfiguration.Builder.class));
397 
398         CodeBlock codeBlock = CodeBlock.builder()
399                                        .addStatement("$T userAgentApplier = b -> b.addApiName($T.builder().version"
400                                                      + "($S).name($S).build())",
401                                                      parameterizedTypeName, ApiName.class,
402                                                      WAITERS_USER_AGENT,
403                                                      "hll")
404                                        .addStatement("$T overrideConfiguration =\n"
405                                                      + "            request.overrideConfiguration().map(c -> c.toBuilder()"
406                                                      + ".applyMutation"
407                                                      + "(userAgentApplier).build())\n"
408                                                      + "            .orElse((AwsRequestOverrideConfiguration.builder()"
409                                                      + ".applyMutation"
410                                                      + "(userAgentApplier).build()))", AwsRequestOverrideConfiguration.class)
411                                        .addStatement("return (T) request.toBuilder().overrideConfiguration"
412                                                      + "(overrideConfiguration).build()")
413                                        .build();
414 
415         return MethodSpec.methodBuilder("applyWaitersUserAgent")
416                          .addModifiers(Modifier.PRIVATE)
417                          .addParameter(typeVariableName, "request")
418                          .addTypeVariable(typeVariableName)
419                          .addCode(codeBlock)
420                          .returns(typeVariableName)
421                          .build();
422     }
423 
getWaiterMethodName(String waiterMethodName)424     private String getWaiterMethodName(String waiterMethodName) {
425         return "waitUntil" + waiterMethodName;
426     }
427 
waiterAcceptorTypeName(WaiterDefinition waiterDefinition)428     private TypeName waiterAcceptorTypeName(WaiterDefinition waiterDefinition) {
429         WildcardTypeName wildcardTypeName = WildcardTypeName.supertypeOf(fullyQualifiedResponseType(waiterDefinition));
430 
431         return ParameterizedTypeName.get(ClassName.get(List.class),
432                                          ParameterizedTypeName.get(ClassName.get(WaiterAcceptor.class), wildcardTypeName));
433     }
434 
fullyQualifiedResponseType(WaiterDefinition waiterDefinition)435     private TypeName fullyQualifiedResponseType(WaiterDefinition waiterDefinition) {
436         String modelPackage = model.getMetadata().getFullModelPackageName();
437         String operationResponseType = model.getOperation(waiterDefinition.getOperation()).getReturnType().getReturnType();
438         return ClassName.get(modelPackage, operationResponseType);
439     }
440 
acceptor(Acceptor acceptor)441     private CodeBlock acceptor(Acceptor acceptor) {
442         CodeBlock.Builder result = CodeBlock.builder();
443 
444         switch (acceptor.getState()) {
445             case "success":
446                 result.add("$T.success", WaiterAcceptor.class);
447                 break;
448             case "failure":
449                 result.add("$T.error", WaiterAcceptor.class);
450                 break;
451             case "retry":
452                 result.add("$T.retry", WaiterAcceptor.class);
453                 break;
454             default:
455                 throw new IllegalArgumentException("Unsupported acceptor state: " + acceptor.getState());
456         }
457 
458         switch (acceptor.getMatcher()) {
459             case "path":
460                 result.add("OnResponseAcceptor(");
461                 result.add(pathAcceptorBody(acceptor));
462                 result.add(")");
463                 break;
464             case "pathAll":
465                 result.add("OnResponseAcceptor(");
466                 result.add(pathAllAcceptorBody(acceptor));
467                 result.add(")");
468                 break;
469             case "pathAny":
470                 result.add("OnResponseAcceptor(");
471                 result.add(pathAnyAcceptorBody(acceptor));
472                 result.add(")");
473                 break;
474             case "status":
475                 // Note: Ignores the result we've built so far because this uses a special acceptor implementation.
476                 int expected = Integer.parseInt(acceptor.getExpected().asText());
477                 return CodeBlock.of("new $T($L, $T.$L)", waitersRuntimeClass().nestedClass("ResponseStatusAcceptor"),
478                                     expected, WaiterState.class, waiterState(acceptor));
479             case "error":
480                 result.add("OnExceptionAcceptor(");
481                 result.add(errorAcceptorBody(acceptor));
482                 result.add(")");
483                 break;
484             default:
485                 throw new IllegalArgumentException("Unsupported acceptor matcher: " + acceptor.getMatcher());
486         }
487 
488         return result.build();
489     }
490 
waiterState(Acceptor acceptor)491     private String waiterState(Acceptor acceptor) {
492         switch (acceptor.getState()) {
493             case "success":
494                 return WaiterState.SUCCESS.name();
495             case "failure":
496                 return WaiterState.FAILURE.name();
497             case "retry":
498                 return WaiterState.RETRY.name();
499             default:
500                 throw new IllegalArgumentException("Unsupported acceptor state: " + acceptor.getState());
501         }
502     }
503 
pathAcceptorBody(Acceptor acceptor)504     private CodeBlock pathAcceptorBody(Acceptor acceptor) {
505         String expected = acceptor.getExpected().asText();
506         String expectedType = acceptor.getExpected() instanceof JrsString ? "$S" : "$L";
507         return CodeBlock.builder()
508                         .add("response -> {")
509                         .add("$1T input = new $1T(response);", waitersRuntimeClass().nestedClass("Value"))
510                         .add("return $T.equals(", Objects.class)
511                         .add(jmesPathAcceptorGenerator.interpret(acceptor.getArgument(), "input"))
512                         .add(".value(), " + expectedType + ");", expected)
513                         .add("}")
514                         .build();
515     }
516 
pathAllAcceptorBody(Acceptor acceptor)517     private CodeBlock pathAllAcceptorBody(Acceptor acceptor) {
518         String expected = acceptor.getExpected().asText();
519         String expectedType = acceptor.getExpected() instanceof JrsString ? "$S" : "$L";
520         return CodeBlock.builder()
521                         .add("response -> {")
522                         .add("$1T input = new $1T(response);", waitersRuntimeClass().nestedClass("Value"))
523                         .add("$T<$T> resultValues = ", List.class, Object.class)
524                         .add(jmesPathAcceptorGenerator.interpret(acceptor.getArgument(), "input"))
525                         .add(".values();")
526                         .add("return !resultValues.isEmpty() && "
527                              + "resultValues.stream().allMatch(v -> $T.equals(v, " + expectedType + "));",
528                              Objects.class, expected)
529                         .add("}")
530                         .build();
531     }
532 
pathAnyAcceptorBody(Acceptor acceptor)533     private CodeBlock pathAnyAcceptorBody(Acceptor acceptor) {
534         String expected = acceptor.getExpected().asText();
535         String expectedType = acceptor.getExpected() instanceof JrsString ? "$S" : "$L";
536         return CodeBlock.builder()
537                         .add("response -> {")
538                         .add("$1T input = new $1T(response);", waitersRuntimeClass().nestedClass("Value"))
539                         .add("$T<$T> resultValues = ", List.class, Object.class)
540                         .add(jmesPathAcceptorGenerator.interpret(acceptor.getArgument(), "input"))
541                         .add(".values();")
542                         .add("return !resultValues.isEmpty() && "
543                              + "resultValues.stream().anyMatch(v -> $T.equals(v, " + expectedType + "));",
544                              Objects.class, expected)
545                         .add("}")
546                         .build();
547     }
548 
errorAcceptorBody(Acceptor acceptor)549     private CodeBlock errorAcceptorBody(Acceptor acceptor) {
550         String expected = acceptor.getExpected().asText();
551         String expectedType = acceptor.getExpected() instanceof JrsString ? "$S" : "$L";
552         return CodeBlock.of("error -> $T.equals(errorCode(error), " + expectedType + ")", Objects.class, expected);
553     }
554 
staticErrorCodeMethod()555     private MethodSpec staticErrorCodeMethod() {
556         return MethodSpec.methodBuilder("errorCode")
557                          .addModifiers(PRIVATE, STATIC)
558                          .returns(String.class)
559                          .addParameter(Throwable.class, "error")
560                          .addCode("if (error instanceof $T) {", AwsServiceException.class)
561                          .addCode("return (($T) error).awsErrorDetails().errorCode();", AwsServiceException.class)
562                          .addCode("}")
563                          .addCode("return null;")
564                          .build();
565     }
566 
waitersRuntimeClass()567     private ClassName waitersRuntimeClass() {
568         return ClassName.get(model.getMetadata().getFullWaitersInternalPackageName(),
569                              WaitersRuntimeGeneratorTask.RUNTIME_CLASS_NAME);
570     }
571 }
572