Skip to content

Commit f852f97

Browse files
jeffaldermillems
authored andcommitted
Add list/map existence check (#867)
1 parent 172b7e0 commit f852f97

22 files changed

+4912
-1966
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"category": "AWS SDK for Java v2",
3+
"type": "feature",
4+
"description": "Adds a `has*` method to requests and responses that have a List or Map property."
5+
}

codegen/src/main/java/software/amazon/awssdk/codegen/AddShapes.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ private MemberModel generateMemberModel(String c2jMemberName, Member c2jMemberDe
179179
.withFluentEnumGetterMethodName(namingStrategy.getFluentEnumGetterMethodName(c2jMemberName, parentShape, shape))
180180
.withFluentSetterMethodName(namingStrategy.getFluentSetterMethodName(c2jMemberName, parentShape, shape))
181181
.withFluentEnumSetterMethodName(namingStrategy.getFluentEnumSetterMethodName(c2jMemberName, parentShape, shape))
182+
.withExistenceCheckMethodName(namingStrategy.getExistenceCheckMethodName(c2jMemberName, parentShape))
182183
.withBeanStyleGetterMethodName(namingStrategy.getBeanStyleGetterMethodName(c2jMemberName, parentShape, shape))
183184
.withBeanStyleSetterMethodName(namingStrategy.getBeanStyleSetterMethodName(c2jMemberName, parentShape, shape));
184185
memberModel.setIdempotencyToken(c2jMemberDefinition.isIdempotencyToken());

codegen/src/main/java/software/amazon/awssdk/codegen/internal/DocumentationUtils.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ public final class DocumentationUtils {
3636

3737
private static final String DEFAULT_GETTER_PARAM = "The value of the %s property for this object.";
3838

39+
private static final String DEFAULT_EXISTENCE_CHECK = "Returns true if the %s property was specified by the sender "
40+
+ "(it may be empty), or false if the sender did not specify "
41+
+ "the value (it will be empty). For responses returned by the SDK, "
42+
+ "the sender is the AWS service.";
43+
3944
private static final String DEFAULT_FLUENT_RETURN =
4045
"Returns a reference to this object so that method calls can be chained together.";
4146

@@ -164,4 +169,8 @@ public static String defaultGetterParam() {
164169
public static String defaultFluentReturn() {
165170
return DEFAULT_FLUENT_RETURN;
166171
}
172+
173+
public static String defaultExistenceCheck() {
174+
return DEFAULT_EXISTENCE_CHECK;
175+
}
167176
}

codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/MemberModel.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package software.amazon.awssdk.codegen.model.intermediate;
1717

1818
import static software.amazon.awssdk.codegen.internal.Constant.LF;
19+
import static software.amazon.awssdk.codegen.internal.DocumentationUtils.defaultExistenceCheck;
1920
import static software.amazon.awssdk.codegen.internal.DocumentationUtils.defaultFluentReturn;
2021
import static software.amazon.awssdk.codegen.internal.DocumentationUtils.defaultGetter;
2122
import static software.amazon.awssdk.codegen.internal.DocumentationUtils.defaultGetterParam;
@@ -24,11 +25,15 @@
2425
import static software.amazon.awssdk.codegen.internal.DocumentationUtils.stripHtmlTags;
2526

2627
import com.fasterxml.jackson.annotation.JsonIgnore;
28+
import com.squareup.javapoet.ClassName;
2729
import java.util.List;
2830
import java.util.Map;
31+
import java.util.Optional;
2932
import software.amazon.awssdk.codegen.internal.TypeUtils;
3033
import software.amazon.awssdk.core.SdkBytes;
3134
import software.amazon.awssdk.core.protocol.MarshallingType;
35+
import software.amazon.awssdk.core.util.SdkAutoConstructList;
36+
import software.amazon.awssdk.core.util.SdkAutoConstructMap;
3237
import software.amazon.awssdk.protocols.core.PathMarshaller;
3338
import software.amazon.awssdk.utils.StringUtils;
3439

@@ -70,6 +75,8 @@ public class MemberModel extends DocumentationModel {
7075

7176
private String fluentEnumSetterMethodName;
7277

78+
private String existenceCheckMethodName;
79+
7380
private String beanStyleGetterName;
7481

7582
private String beanStyleSetterName;
@@ -247,6 +254,19 @@ public MemberModel withFluentEnumSetterMethodName(String fluentEnumSetterMethodN
247254
return this;
248255
}
249256

257+
public String getExistenceCheckMethodName() {
258+
return existenceCheckMethodName;
259+
}
260+
261+
public void setExistenceCheckMethodName(String existenceCheckMethodName) {
262+
this.existenceCheckMethodName = existenceCheckMethodName;
263+
}
264+
265+
public MemberModel withExistenceCheckMethodName(String existenceCheckMethodName) {
266+
setExistenceCheckMethodName(existenceCheckMethodName);
267+
return this;
268+
}
269+
250270
public ReturnTypeModel getGetterModel() {
251271
return getterModel;
252272
}
@@ -408,6 +428,12 @@ public String getGetterDocumentation() {
408428
}
409429
}
410430

431+
if (getAutoConstructClassIfExists().isPresent()) {
432+
appendParagraph(docBuilder,
433+
"You can use {@link #%s()} to see if a value was sent in this field.",
434+
getExistenceCheckMethodName());
435+
}
436+
411437
String variableDesc = StringUtils.isNotBlank(documentation) ? documentation : defaultGetterParam().replace("%s", name);
412438

413439
docBuilder.append("@return ")
@@ -429,6 +455,10 @@ public String getFluentSetterDocumentation() {
429455
+ getEnumDoc();
430456
}
431457

458+
public String getExistenceCheckDocumentation() {
459+
return defaultExistenceCheck().replace("%s", name) + LF;
460+
}
461+
432462
public String getDefaultConsumerFluentSetterDocumentation() {
433463
return (StringUtils.isNotBlank(documentation) ? documentation : defaultSetter().replace("%s", name) + "\n")
434464
+ LF
@@ -613,4 +643,14 @@ private void appendParagraph(StringBuilder builder, String content, Object... co
613643
.append("</p>")
614644
.append(LF);
615645
}
646+
647+
public Optional<ClassName> getAutoConstructClassIfExists() {
648+
if (isList()) {
649+
return Optional.of(ClassName.get(SdkAutoConstructList.class));
650+
} else if (isMap()) {
651+
return Optional.of(ClassName.get(SdkAutoConstructMap.class));
652+
}
653+
654+
return Optional.empty();
655+
}
616656
}

codegen/src/main/java/software/amazon/awssdk/codegen/naming/DefaultNamingStrategy.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,13 @@ public String getFluentEnumGetterMethodName(String memberName, Shape parentShape
292292
return getterMethodName;
293293
}
294294

295+
@Override
296+
public String getExistenceCheckMethodName(String memberName, Shape parentShape) {
297+
String existenceCheckMethodName = Utils.unCapitalize(memberName);
298+
existenceCheckMethodName = rewriteInvalidMemberName(existenceCheckMethodName, parentShape);
299+
return String.format("has%s", Utils.capitalize(existenceCheckMethodName));
300+
}
301+
295302
@Override
296303
public String getBeanStyleGetterMethodName(String memberName, Shape parentShape, Shape c2jShape) {
297304
String fluentGetterMethodName = getFluentGetterMethodName(memberName, parentShape, c2jShape);

codegen/src/main/java/software/amazon/awssdk/codegen/naming/NamingStrategy.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,4 +146,13 @@ public interface NamingStrategy {
146146
* @return Name of field for {@link SdkField} pojo.
147147
*/
148148
String getSdkFieldFieldName(MemberModel memberModel);
149+
150+
/**
151+
* Names a method that would check for existence of the member in the response.
152+
*
153+
* @param memberName The member name to get the method name for.
154+
* @param parentShape The shape containing the member.
155+
* @return Name of an existence check method.
156+
*/
157+
String getExistenceCheckMethodName(String memberName, Shape parentShape);
149158
}

codegen/src/main/java/software/amazon/awssdk/codegen/poet/model/AwsServiceModel.java

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -233,12 +233,12 @@ private MethodSpec.Builder acceptMethodSpec(ClassName modelClass, ClassName resp
233233
return MethodSpec.methodBuilder("accept")
234234
.addModifiers(PUBLIC)
235235
.addJavadoc(new DocumentationBuilder()
236-
.description("Calls the appropriate visit method depending on "
237-
+ "the subtype of {@link $T}.")
238-
.param("visitor", "Visitor to invoke.")
239-
.build(), modelClass)
236+
.description("Calls the appropriate visit method depending on "
237+
+ "the subtype of {@link $T}.")
238+
.param("visitor", "Visitor to invoke.")
239+
.build(), modelClass)
240240
.addParameter(responseHandlerClass
241-
.nestedClass("Visitor"), "visitor");
241+
.nestedClass("Visitor"), "visitor");
242242
}
243243

244244
@Override
@@ -295,7 +295,7 @@ private TypeName responseBaseClass() {
295295

296296
private ClassName exceptionBaseClass() {
297297
String customExceptionBase = intermediateModel.getCustomizationConfig()
298-
.getSdkModeledExceptionBaseClassName();
298+
.getSdkModeledExceptionBaseClassName();
299299
if (customExceptionBase != null) {
300300
return poetExtensions.getModelClass(customExceptionBase);
301301
}
@@ -304,8 +304,8 @@ private ClassName exceptionBaseClass() {
304304

305305
private TypeName toCopyableBuilderInterface() {
306306
return ParameterizedTypeName.get(ClassName.get(ToCopyableBuilder.class),
307-
className().nestedClass("Builder"),
308-
className());
307+
className().nestedClass("Builder"),
308+
className());
309309
}
310310

311311
private List<MethodSpec> modelClassMethods() {
@@ -381,14 +381,16 @@ private Stream<MethodSpec> memberGetters(MemberModel member) {
381381
result.add(enumMemberGetter(member));
382382
}
383383

384+
member.getAutoConstructClassIfExists()
385+
.ifPresent(autoConstructClass -> result.add(existenceCheckGetter(member, autoConstructClass)));
386+
384387
result.add(memberGetter(member));
385388

386389
return result.stream();
387390
}
388391

389392
private boolean shouldGenerateEnumGetter(MemberModel member) {
390393
return member.getEnumType() != null || MemberCopierSpec.isEnumCopyAvailable(member);
391-
392394
}
393395

394396
private MethodSpec enumMemberGetter(MemberModel member) {
@@ -409,6 +411,20 @@ private MethodSpec memberGetter(MemberModel member) {
409411
.build();
410412
}
411413

414+
private MethodSpec existenceCheckGetter(MemberModel member, ClassName autoConstructClass) {
415+
return MethodSpec.methodBuilder(member.getExistenceCheckMethodName())
416+
.addJavadoc("$L", member.getExistenceCheckDocumentation())
417+
.addModifiers(PUBLIC)
418+
.returns(TypeName.BOOLEAN)
419+
.addCode(existenceCheckStatement(member, autoConstructClass))
420+
.build();
421+
}
422+
423+
private CodeBlock existenceCheckStatement(MemberModel member, ClassName autoConstructClass) {
424+
String variableName = member.getVariable().getVariableName();
425+
return CodeBlock.of("return $N != null && !($N instanceof $T);", variableName, variableName, autoConstructClass);
426+
}
427+
412428
private CodeBlock enumGetterStatement(MemberModel member) {
413429
String fieldName = member.getVariable().getVariableName();
414430
if (member.isList() || member.isMap()) {

0 commit comments

Comments
 (0)